diff --git a/cookbook/admin.py b/cookbook/admin.py
index 64a03173..fcc007bc 100644
--- a/cookbook/admin.py
+++ b/cookbook/admin.py
@@ -16,7 +16,7 @@ from .models import (BookmarkletImport, Comment, CookLog, Food, ImportLog, Ingre
ShoppingList, ShoppingListEntry, ShoppingListRecipe, Space, Step, Storage,
Supermarket, SupermarketCategory, SupermarketCategoryRelation, Sync, SyncLog,
TelegramBot, Unit, UnitConversion, UserFile, UserPreference, UserSpace,
- ViewLog, HomeAssistantConfig, ExampleConfig)
+ ViewLog, ConnectorConfig)
class CustomUserAdmin(UserAdmin):
@@ -95,20 +95,12 @@ class StorageAdmin(admin.ModelAdmin):
admin.site.register(Storage, StorageAdmin)
-class HomeAssistantConfigAdmin(admin.ModelAdmin):
- list_display = ('id', 'name', 'enabled', 'url')
+class ConnectorConfigAdmin(admin.ModelAdmin):
+ list_display = ('id', 'name', 'type', 'enabled', 'url')
search_fields = ('name', 'url')
-admin.site.register(HomeAssistantConfig, HomeAssistantConfigAdmin)
-
-
-class ExampleConfigAdmin(admin.ModelAdmin):
- list_display = ('id', 'name', 'enabled', 'feed_url')
- search_fields = ('name',)
-
-
-admin.site.register(ExampleConfig, ExampleConfigAdmin)
+admin.site.register(ConnectorConfig, ConnectorConfigAdmin)
class SyncAdmin(admin.ModelAdmin):
diff --git a/cookbook/connectors/connector.py b/cookbook/connectors/connector.py
index 4ed61350..3647dc71 100644
--- a/cookbook/connectors/connector.py
+++ b/cookbook/connectors/connector.py
@@ -1,9 +1,13 @@
from abc import ABC, abstractmethod
-from cookbook.models import ShoppingListEntry, Space
+from cookbook.models import ShoppingListEntry, Space, ConnectorConfig
class Connector(ABC):
+ @abstractmethod
+ def __init__(self, config: ConnectorConfig):
+ pass
+
@abstractmethod
async def on_shopping_list_entry_created(self, space: Space, instance: ShoppingListEntry) -> None:
pass
@@ -20,5 +24,4 @@ class Connector(ABC):
async def close(self) -> None:
pass
- # TODO: Maybe add an 'IsEnabled(self) -> Bool' to here
# TODO: Add Recipes & possibly Meal Place listeners/hooks (And maybe more?)
diff --git a/cookbook/connectors/connector_manager.py b/cookbook/connectors/connector_manager.py
index 178c8efb..86a60b04 100644
--- a/cookbook/connectors/connector_manager.py
+++ b/cookbook/connectors/connector_manager.py
@@ -12,15 +12,13 @@ from typing import List, Any, Dict, Optional
from django_scopes import scope
from cookbook.connectors.connector import Connector
-from cookbook.connectors.example import Example
from cookbook.connectors.homeassistant import HomeAssistant
-from cookbook.models import ShoppingListEntry, Recipe, MealPlan, Space, HomeAssistantConfig, ExampleConfig
+from cookbook.models import ShoppingListEntry, Recipe, MealPlan, Space, ConnectorConfig
multiprocessing.set_start_method('fork') # https://code.djangoproject.com/ticket/31169
QUEUE_MAX_SIZE = 25
REGISTERED_CLASSES: UnionType = ShoppingListEntry | Recipe | MealPlan
-CONNECTOR_UPDATE_CLASSES: UnionType = HomeAssistantConfig | ExampleConfig
class ActionType(Enum):
@@ -37,7 +35,7 @@ class Work:
class ConnectorManager:
_queue: JoinableQueue
- _listening_to_classes = REGISTERED_CLASSES | CONNECTOR_UPDATE_CLASSES
+ _listening_to_classes = REGISTERED_CLASSES | ConnectorConfig
def __init__(self):
self._queue = multiprocessing.JoinableQueue(maxsize=QUEUE_MAX_SIZE)
@@ -88,7 +86,7 @@ class ConnectorManager:
break
# If a Connector was changed/updated, refresh connector from the database for said space
- refresh_connector_cache = isinstance(item.instance, CONNECTOR_UPDATE_CLASSES)
+ refresh_connector_cache = isinstance(item.instance, ConnectorConfig)
space: Space = item.instance.space
connectors: Optional[List[Connector]] = _connectors.get(space.name)
@@ -98,10 +96,11 @@ class ConnectorManager:
loop.run_until_complete(close_connectors(connectors))
with scope(space=space):
- connectors: List[Connector] = [
- *(HomeAssistant(config) for config in space.homeassistantconfig_set.all() if config.enabled),
- *(Example(config) for config in space.exampleconfig_set.all() if config.enabled)
- ]
+ connectors: List[Connector] = list(
+ filter(
+ lambda x: x is not None,
+ [ConnectorManager.get_connected_for_config(config) for config in space.connectorconfig_set.all() if config.enabled],
+ ))
_connectors[space.name] = connectors
if len(connectors) == 0 or refresh_connector_cache:
@@ -113,6 +112,14 @@ class ConnectorManager:
loop.close()
+ @staticmethod
+ def get_connected_for_config(config: ConnectorConfig) -> Optional[Connector]:
+ match config.type:
+ case ConnectorConfig.HOMEASSISTANT:
+ return HomeAssistant(config)
+ case _:
+ return None
+
async def close_connectors(connectors: List[Connector]):
tasks: List[Task] = [asyncio.create_task(connector.close()) for connector in connectors]
diff --git a/cookbook/connectors/example.py b/cookbook/connectors/example.py
deleted file mode 100644
index ce55f476..00000000
--- a/cookbook/connectors/example.py
+++ /dev/null
@@ -1,27 +0,0 @@
-from cookbook.connectors.connector import Connector
-from cookbook.models import ExampleConfig, Space, ShoppingListEntry
-
-
-class Example(Connector):
- _config: ExampleConfig
-
- def __init__(self, config: ExampleConfig):
- self._config = config
-
- 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:
- return
- pass
-
- async def on_shopping_list_entry_updated(self, space: Space, shopping_list_entry: ShoppingListEntry) -> None:
- if not self._config.on_shopping_list_entry_updated_enabled:
- return
- pass
-
- async def on_shopping_list_entry_deleted(self, space: Space, shopping_list_entry: ShoppingListEntry) -> None:
- if not self._config.on_shopping_list_entry_deleted_enabled:
- return
- pass
-
- async def close(self) -> None:
- pass
diff --git a/cookbook/connectors/homeassistant.py b/cookbook/connectors/homeassistant.py
index e394e974..e653c3f3 100644
--- a/cookbook/connectors/homeassistant.py
+++ b/cookbook/connectors/homeassistant.py
@@ -4,16 +4,19 @@ from logging import Logger
from homeassistant_api import Client, HomeassistantAPIError, Domain
from cookbook.connectors.connector import Connector
-from cookbook.models import ShoppingListEntry, HomeAssistantConfig, Space
+from cookbook.models import ShoppingListEntry, ConnectorConfig, Space
class HomeAssistant(Connector):
_domains_cache: dict[str, Domain]
- _config: HomeAssistantConfig
+ _config: ConnectorConfig
_logger: Logger
_client: Client
- def __init__(self, config: HomeAssistantConfig):
+ 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()
self._config = config
self._logger = logging.getLogger("connector.HomeAssistant")
diff --git a/cookbook/forms.py b/cookbook/forms.py
index 18867958..2244acad 100644
--- a/cookbook/forms.py
+++ b/cookbook/forms.py
@@ -10,7 +10,7 @@ from django_scopes.forms import SafeModelChoiceField, SafeModelMultipleChoiceFie
from hcaptcha.fields import hCaptchaField
from .models import (Comment, Food, InviteLink, Keyword, Recipe, RecipeBook, RecipeBookEntry,
- SearchPreference, Space, Storage, Sync, User, UserPreference, HomeAssistantConfig, ExampleConfig)
+ SearchPreference, Space, Storage, Sync, User, UserPreference, ConnectorConfig)
class SelectWidget(widgets.Select):
@@ -209,11 +209,6 @@ class ConnectorConfigForm(forms.ModelForm):
required=False,
)
- class Meta:
- fields = ('name', 'enabled', 'on_shopping_list_entry_created_enabled', 'on_shopping_list_entry_updated_enabled', 'on_shopping_list_entry_deleted_enabled')
-
-
-class HomeAssistantConfigForm(ConnectorConfigForm):
update_token = forms.CharField(
widget=forms.TextInput(attrs={'autocomplete': 'update-token', 'type': 'password'}),
required=False,
@@ -221,45 +216,23 @@ class HomeAssistantConfigForm(ConnectorConfigForm):
)
url = forms.URLField(
- required=True,
+ required=False,
help_text=_('Something like http://homeassistant.local:8123/api'),
)
- on_shopping_list_entry_created_enabled = forms.BooleanField(
- help_text="Enable syncing ShoppingListEntry to Homeassistant Todo List -- Warning: Might have negative performance impact",
- required=False,
- )
-
- on_shopping_list_entry_updated_enabled = forms.BooleanField(
- help_text="PLACEHOLDER",
- required=False,
- )
-
- on_shopping_list_entry_deleted_enabled = forms.BooleanField(
- help_text="Enable syncing ShoppingListEntry deletion to Homeassistant Todo List -- Warning: Might have negative performance impact",
- required=False,
- )
-
class Meta:
- model = HomeAssistantConfig
+ model = ConnectorConfig
- fields = ConnectorConfigForm.Meta.fields + ('url', 'todo_entity')
+ fields = (
+ 'name', 'type', 'enabled', 'on_shopping_list_entry_created_enabled', 'on_shopping_list_entry_updated_enabled',
+ 'on_shopping_list_entry_deleted_enabled', 'url', 'todo_entity',
+ )
help_texts = {
'url': _('http://homeassistant.local:8123/api for example'),
}
-class ExampleConfigForm(ConnectorConfigForm):
- feed_url = forms.URLField(
- required=False,
- )
-
- class Meta:
- model = ExampleConfig
- fields = ConnectorConfigForm.Meta.fields + ('feed_url',)
-
-
# TODO: Deprecate
class RecipeBookEntryForm(forms.ModelForm):
prefix = 'bookmark'
diff --git a/cookbook/migrations/0208_connectorconfig.py b/cookbook/migrations/0208_connectorconfig.py
new file mode 100644
index 00000000..c29c0253
--- /dev/null
+++ b/cookbook/migrations/0208_connectorconfig.py
@@ -0,0 +1,36 @@
+# Generated by Django 4.2.7 on 2024-01-17 21:12
+
+import cookbook.models
+from django.conf import settings
+import django.core.validators
+from django.db import migrations, models
+import django.db.models.deletion
+
+
+class Migration(migrations.Migration):
+
+ dependencies = [
+ migrations.swappable_dependency(settings.AUTH_USER_MODEL),
+ ('cookbook', '0207_space_logo_color_128_space_logo_color_144_and_more'),
+ ]
+
+ operations = [
+ migrations.CreateModel(
+ name='ConnectorConfig',
+ fields=[
+ ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
+ ('name', models.CharField(max_length=128, validators=[django.core.validators.MinLengthValidator(1)])),
+ ('type', models.CharField(choices=[('HomeAssistant', 'HomeAssistant')], default='HomeAssistant', max_length=128)),
+ ('enabled', models.BooleanField(default=True, help_text='Is Connector Enabled')),
+ ('on_shopping_list_entry_created_enabled', models.BooleanField(default=False)),
+ ('on_shopping_list_entry_updated_enabled', models.BooleanField(default=False)),
+ ('on_shopping_list_entry_deleted_enabled', models.BooleanField(default=False)),
+ ('url', models.URLField(blank=True, null=True)),
+ ('token', models.CharField(blank=True, max_length=512, null=True)),
+ ('todo_entity', models.CharField(blank=True, max_length=128, null=True)),
+ ('created_by', models.ForeignKey(on_delete=django.db.models.deletion.PROTECT, to=settings.AUTH_USER_MODEL)),
+ ('space', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='cookbook.space')),
+ ],
+ bases=(models.Model, cookbook.models.PermissionModelMixin),
+ ),
+ ]
diff --git a/cookbook/migrations/0208_homeassistantconfig_exampleconfig.py b/cookbook/migrations/0208_homeassistantconfig_exampleconfig.py
deleted file mode 100644
index 789094c1..00000000
--- a/cookbook/migrations/0208_homeassistantconfig_exampleconfig.py
+++ /dev/null
@@ -1,56 +0,0 @@
-# Generated by Django 4.2.7 on 2024-01-14 16:00
-
-import cookbook.models
-from django.conf import settings
-import django.core.validators
-from django.db import migrations, models
-import django.db.models.deletion
-
-
-class Migration(migrations.Migration):
-
- dependencies = [
- migrations.swappable_dependency(settings.AUTH_USER_MODEL),
- ('cookbook', '0207_space_logo_color_128_space_logo_color_144_and_more'),
- ]
-
- operations = [
- migrations.CreateModel(
- name='HomeAssistantConfig',
- fields=[
- ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
- ('name', models.CharField(max_length=128, validators=[django.core.validators.MinLengthValidator(1)])),
- ('enabled', models.BooleanField(default=True, help_text='Is Connector Enabled')),
- ('on_shopping_list_entry_created_enabled', models.BooleanField(default=False)),
- ('on_shopping_list_entry_updated_enabled', models.BooleanField(default=False)),
- ('on_shopping_list_entry_deleted_enabled', models.BooleanField(default=False)),
- ('url', models.URLField(blank=True)),
- ('token', models.CharField(blank=True, max_length=512)),
- ('todo_entity', models.CharField(default='todo.shopping_list', max_length=128)),
- ('created_by', models.ForeignKey(on_delete=django.db.models.deletion.PROTECT, to=settings.AUTH_USER_MODEL)),
- ('space', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='cookbook.space')),
- ],
- options={
- 'abstract': False,
- },
- bases=(models.Model, cookbook.models.PermissionModelMixin),
- ),
- migrations.CreateModel(
- name='ExampleConfig',
- fields=[
- ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
- ('name', models.CharField(max_length=128, validators=[django.core.validators.MinLengthValidator(1)])),
- ('enabled', models.BooleanField(default=True, help_text='Is Connector Enabled')),
- ('on_shopping_list_entry_created_enabled', models.BooleanField(default=False)),
- ('on_shopping_list_entry_updated_enabled', models.BooleanField(default=False)),
- ('on_shopping_list_entry_deleted_enabled', models.BooleanField(default=False)),
- ('feed_url', models.URLField(blank=True)),
- ('created_by', models.ForeignKey(on_delete=django.db.models.deletion.PROTECT, to=settings.AUTH_USER_MODEL)),
- ('space', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='cookbook.space')),
- ],
- options={
- 'abstract': False,
- },
- bases=(models.Model, cookbook.models.PermissionModelMixin),
- ),
- ]
diff --git a/cookbook/models.py b/cookbook/models.py
index a41dc37f..f0bc1af6 100644
--- a/cookbook/models.py
+++ b/cookbook/models.py
@@ -339,8 +339,7 @@ class Space(ExportModelOperationsMixin('space'), models.Model):
SyncLog.objects.filter(sync__space=self).delete()
Sync.objects.filter(space=self).delete()
Storage.objects.filter(space=self).delete()
- HomeAssistantConfig.objects.filter(space=self).delete()
- ExampleConfig.objects.filter(space=self).delete()
+ ConnectorConfig.objects.filter(space=self).delete()
ShoppingListEntry.objects.filter(shoppinglist__space=self).delete()
ShoppingListRecipe.objects.filter(shoppinglist__space=self).delete()
@@ -366,30 +365,28 @@ class Space(ExportModelOperationsMixin('space'), models.Model):
class ConnectorConfig(models.Model, PermissionModelMixin):
+ HOMEASSISTANT = 'HomeAssistant'
+ CONNECTER_TYPE = ((HOMEASSISTANT, 'HomeAssistant'),)
+
name = models.CharField(max_length=128, validators=[MinLengthValidator(1)])
+ type = models.CharField(
+ choices=CONNECTER_TYPE, max_length=128, default=HOMEASSISTANT
+ )
enabled = models.BooleanField(default=True, help_text="Is Connector Enabled")
on_shopping_list_entry_created_enabled = models.BooleanField(default=False)
on_shopping_list_entry_updated_enabled = models.BooleanField(default=False)
on_shopping_list_entry_deleted_enabled = models.BooleanField(default=False)
+ url = models.URLField(blank=True, null=True)
+ token = models.CharField(max_length=512, blank=True, null=True)
+ todo_entity = models.CharField(max_length=128, blank=True, null=True)
+
created_by = models.ForeignKey(User, on_delete=models.PROTECT)
space = models.ForeignKey(Space, on_delete=models.CASCADE)
objects = ScopedManager(space='space')
- class Meta:
- abstract = True
-
-
-class HomeAssistantConfig(ConnectorConfig):
- url = models.URLField(blank=True)
- token = models.CharField(max_length=512, blank=True)
- todo_entity = models.CharField(max_length=128, default='todo.shopping_list')
-
-
-class ExampleConfig(ConnectorConfig):
- feed_url = models.URLField(blank=True)
class UserPreference(models.Model, PermissionModelMixin):
diff --git a/cookbook/serializer.py b/cookbook/serializer.py
index 53fc8eeb..e102c46f 100644
--- a/cookbook/serializer.py
+++ b/cookbook/serializer.py
@@ -34,7 +34,7 @@ from cookbook.models import (Automation, BookmarkletImport, Comment, CookLog, Cu
ShareLink, ShoppingList, ShoppingListEntry, ShoppingListRecipe, Space,
Step, Storage, Supermarket, SupermarketCategory,
SupermarketCategoryRelation, Sync, SyncLog, Unit, UnitConversion,
- UserFile, UserPreference, UserSpace, ViewLog, HomeAssistantConfig)
+ UserFile, UserPreference, UserSpace, ViewLog, ConnectorConfig)
from cookbook.templatetags.custom_tags import markdown
from recipes.settings import AWS_ENABLED, MEDIA_URL
@@ -413,14 +413,14 @@ class StorageSerializer(SpacedModelSerializer):
}
-class HomeAssistantConfigSerializer(SpacedModelSerializer):
+class ConnectorConfigConfigSerializer(SpacedModelSerializer):
def create(self, validated_data):
validated_data['created_by'] = self.context['request'].user
return super().create(validated_data)
class Meta:
- model = HomeAssistantConfig
+ model = ConnectorConfig
fields = (
'id', 'name', 'url', 'token', 'todo_entity', 'enabled',
'on_shopping_list_entry_created_enabled', 'on_shopping_list_entry_updated_enabled',
diff --git a/cookbook/tables.py b/cookbook/tables.py
index ba14fbbe..3fcec9c3 100644
--- a/cookbook/tables.py
+++ b/cookbook/tables.py
@@ -1,14 +1,9 @@
-from typing import Any, Dict
-
import django_tables2 as tables
from django.utils.html import format_html
from django.utils.translation import gettext as _
-from django.views.generic import TemplateView
-from django_tables2 import MultiTableMixin
from django_tables2.utils import A
-from .helper.permission_helper import GroupRequiredMixin
-from .models import CookLog, InviteLink, RecipeImport, Storage, Sync, SyncLog, ViewLog, HomeAssistantConfig, ExampleConfig
+from .models import CookLog, InviteLink, RecipeImport, Storage, Sync, SyncLog, ViewLog, ConnectorConfig
class StorageTable(tables.Table):
@@ -20,47 +15,13 @@ class StorageTable(tables.Table):
fields = ('id', 'name', 'method')
-class ExampleConfigTable(tables.Table):
- id = tables.LinkColumn('edit_example_config', args=[A('id')])
+class ConnectorConfigTable(tables.Table):
+ id = tables.LinkColumn('edit_connector_config', args=[A('id')])
class Meta:
- model = ExampleConfig
+ model = ConnectorConfig
template_name = 'generic/table_template.html'
- fields = ('id', 'name', 'enabled', 'feed_url')
- attrs = {'table_name': "Example Configs", 'create_url': 'new_example_config'}
-
-
-class HomeAssistantConfigTable(tables.Table):
- id = tables.LinkColumn('edit_home_assistant_config', args=[A('id')])
-
- class Meta:
- model = HomeAssistantConfig
- template_name = 'generic/table_template.html'
- fields = ('id', 'name', 'enabled', 'url')
- attrs = {'table_name': "HomeAssistant Configs", 'create_url': 'new_home_assistant_config'}
-
-
-class ConnectorConfigTable(GroupRequiredMixin, MultiTableMixin, TemplateView):
- groups_required = ['admin']
- template_name = "list_connectors.html"
-
- table_pagination = {
- "per_page": 25
- }
-
- def get_context_data(self, **kwargs: Any) -> Dict[str, Any]:
- kwargs = super().get_context_data(**kwargs)
- kwargs['title'] = _("Connectors")
- return kwargs
-
- def get_tables(self):
- example_configs = ExampleConfig.objects.filter(space=self.request.space).all()
- home_assistant_configs = HomeAssistantConfig.objects.filter(space=self.request.space).all()
-
- return [
- ExampleConfigTable(example_configs),
- HomeAssistantConfigTable(home_assistant_configs)
- ]
+ fields = ('id', 'name', 'type', 'enabled')
class ImportLogTable(tables.Table):
diff --git a/cookbook/templates/base.html b/cookbook/templates/base.html
index 7fbb14db..6505b405 100644
--- a/cookbook/templates/base.html
+++ b/cookbook/templates/base.html
@@ -336,7 +336,7 @@
class="fas fa-server fa-fw"> {% trans 'Space Settings' %}
{% endif %}
{% if request.user == request.space.created_by or user.is_superuser %}
- {% trans 'External Connectors' %}
{% endif %}
{% if user.is_superuser %}
diff --git a/cookbook/tests/api/test_api_home_assistant_config.py b/cookbook/tests/api/test_api_home_assistant_config.py
index a3de0f45..79f496bf 100644
--- a/cookbook/tests/api/test_api_home_assistant_config.py
+++ b/cookbook/tests/api/test_api_home_assistant_config.py
@@ -5,21 +5,21 @@ from django.contrib import auth
from django.urls import reverse
from django_scopes import scopes_disabled
-from cookbook.models import HomeAssistantConfig
+from cookbook.models import ConnectorConfig
-LIST_URL = 'api:homeassistantconfig-list'
-DETAIL_URL = 'api:homeassistantconfig-detail'
+LIST_URL = 'api:connectorconfig-list'
+DETAIL_URL = 'api:connectorconfig-detail'
@pytest.fixture()
def obj_1(space_1, u1_s1):
- return HomeAssistantConfig.objects.create(
+ return ConnectorConfig.objects.create(
name='HomeAssistant 1', token='token', url='url', todo_entity='todo.shopping_list', enabled=True, created_by=auth.get_user(u1_s1), space=space_1, )
@pytest.fixture
def obj_2(space_1, u1_s1):
- return HomeAssistantConfig.objects.create(
+ return ConnectorConfig.objects.create(
name='HomeAssistant 2', token='token', url='url', todo_entity='todo.shopping_list', enabled=True, created_by=auth.get_user(u1_s1), space=space_1, )
@@ -123,4 +123,4 @@ def test_delete(a1_s1, a1_s2, obj_1):
assert r.status_code == 204
with scopes_disabled():
- assert HomeAssistantConfig.objects.count() == 0
+ assert ConnectorConfig.objects.count() == 0
diff --git a/cookbook/tests/edits/test_edits_home_assistant_config.py b/cookbook/tests/edits/test_edits_connector_config.py
similarity index 77%
rename from cookbook/tests/edits/test_edits_home_assistant_config.py
rename to cookbook/tests/edits/test_edits_connector_config.py
index 0df507de..aad00f8d 100644
--- a/cookbook/tests/edits/test_edits_home_assistant_config.py
+++ b/cookbook/tests/edits/test_edits_connector_config.py
@@ -3,16 +3,18 @@ from django.contrib import auth
from django.contrib import messages
from django.contrib.messages import get_messages
from django.urls import reverse
+from pytest_django.asserts import assertTemplateUsed
-from cookbook.models import HomeAssistantConfig
+from cookbook.models import ConnectorConfig
-EDIT_VIEW_NAME = 'edit_home_assistant_config'
+EDIT_VIEW_NAME = 'edit_connector_config'
@pytest.fixture
def home_assistant_config_obj(a1_s1, space_1):
- return HomeAssistantConfig.objects.create(
+ return ConnectorConfig.objects.create(
name='HomeAssistant 1',
+ type=ConnectorConfig.HOMEASSISTANT,
token='token',
url='http://localhost:8123/api',
todo_entity='todo.shopping_list',
@@ -22,20 +24,22 @@ def home_assistant_config_obj(a1_s1, space_1):
)
-def test_edit_home_assistant_config(home_assistant_config_obj: HomeAssistantConfig, a1_s1, a1_s2):
+def test_edit_connector_config_homeassistant(home_assistant_config_obj: ConnectorConfig, a1_s1, a1_s2):
new_token = '1234_token'
r = a1_s1.post(
reverse(EDIT_VIEW_NAME, args={home_assistant_config_obj.pk}),
{
'name': home_assistant_config_obj.name,
+ 'type': home_assistant_config_obj.type,
'url': home_assistant_config_obj.url,
- 'token': new_token,
+ 'update_token': new_token,
'todo_entity': home_assistant_config_obj.todo_entity,
'enabled': home_assistant_config_obj.enabled,
}
)
- assert r.status_code == 200
+ assert r.status_code == 302
+
r_messages = [m for m in get_messages(r.wsgi_request)]
assert not any(m.level > messages.SUCCESS for m in r_messages)
@@ -46,9 +50,10 @@ def test_edit_home_assistant_config(home_assistant_config_obj: HomeAssistantConf
reverse(EDIT_VIEW_NAME, args={home_assistant_config_obj.pk}),
{
'name': home_assistant_config_obj.name,
+ 'type': home_assistant_config_obj.type,
'url': home_assistant_config_obj.url,
'todo_entity': home_assistant_config_obj.todo_entity,
- 'token': new_token,
+ 'update_token': new_token,
'enabled': home_assistant_config_obj.enabled,
}
)
diff --git a/cookbook/urls.py b/cookbook/urls.py
index e85cfbaf..54d29ae4 100644
--- a/cookbook/urls.py
+++ b/cookbook/urls.py
@@ -12,8 +12,7 @@ from recipes.settings import DEBUG, PLUGINS
from .models import (Automation, Comment, CustomFilter, Food, InviteLink, Keyword, PropertyType,
Recipe, RecipeBook, RecipeBookEntry, RecipeImport, ShoppingList, Space, Step,
Storage, Supermarket, SupermarketCategory, Sync, SyncLog, Unit, UnitConversion,
- UserFile, UserSpace, get_model_name, HomeAssistantConfig, ExampleConfig)
-from .tables import ConnectorConfigTable
+ UserFile, UserSpace, get_model_name, ConnectorConfig)
from .views import api, data, delete, edit, import_export, lists, new, telegram, views
from .views.api import CustomAuthToken, ImportOpenData
@@ -52,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.HomeAssistantConfigViewSet)
+router.register(r'home-assistant-config', api.ConnectorConfigConfigViewSet)
router.register(r'supermarket', api.SupermarketViewSet)
router.register(r'supermarket-category', api.SupermarketCategoryViewSet)
router.register(r'supermarket-category-relation', api.SupermarketCategoryRelationViewSet)
@@ -116,7 +115,6 @@ urlpatterns = [
path('edit/recipe/convert//', edit.convert_recipe, name='edit_convert_recipe'),
path('edit/storage//', edit.edit_storage, name='edit_storage'),
- path('list/connectors', ConnectorConfigTable.as_view(), name='list_connectors'),
path('delete/recipe-source//', delete.delete_recipe_source, name='delete_recipe_source'),
@@ -169,7 +167,7 @@ urlpatterns = [
]
generic_models = (
- Recipe, RecipeImport, Storage, HomeAssistantConfig, ExampleConfig, RecipeBook, SyncLog, Sync,
+ Recipe, RecipeImport, Storage, ConnectorConfig, RecipeBook, SyncLog, Sync,
Comment, RecipeBookEntry, ShoppingList, InviteLink, UserSpace, Space
)
diff --git a/cookbook/views/api.py b/cookbook/views/api.py
index d2cf65f8..bffc3ee4 100644
--- a/cookbook/views/api.py
+++ b/cookbook/views/api.py
@@ -76,7 +76,7 @@ from cookbook.models import (Automation, BookmarkletImport, CookLog, CustomFilte
ShoppingListEntry, ShoppingListRecipe, Space, Step, Storage,
Supermarket, SupermarketCategory, SupermarketCategoryRelation, Sync,
SyncLog, Unit, UnitConversion, UserFile, UserPreference, UserSpace,
- ViewLog, HomeAssistantConfig)
+ ViewLog, ConnectorConfig)
from cookbook.provider.dropbox import Dropbox
from cookbook.provider.local import Local
from cookbook.provider.nextcloud import Nextcloud
@@ -102,7 +102,7 @@ from cookbook.serializer import (AccessTokenSerializer, AutomationSerializer,
SupermarketCategorySerializer, SupermarketSerializer,
SyncLogSerializer, SyncSerializer, UnitConversionSerializer,
UnitSerializer, UserFileSerializer, UserPreferenceSerializer,
- UserSerializer, UserSpaceSerializer, ViewLogSerializer, HomeAssistantConfigSerializer)
+ UserSerializer, UserSpaceSerializer, ViewLogSerializer, ConnectorConfigConfigSerializer)
from cookbook.views.import_export import get_integration
from recipes import settings
from recipes.settings import FDC_API_KEY, DRF_THROTTLE_RECIPE_URL_IMPORT
@@ -464,10 +464,9 @@ class StorageViewSet(viewsets.ModelViewSet):
return self.queryset.filter(space=self.request.space)
-class HomeAssistantConfigViewSet(viewsets.ModelViewSet):
- # TODO handle delete protect error and adjust test
- queryset = HomeAssistantConfig.objects
- serializer_class = HomeAssistantConfigSerializer
+class ConnectorConfigConfigViewSet(viewsets.ModelViewSet):
+ queryset = ConnectorConfig.objects
+ serializer_class = ConnectorConfigConfigSerializer
permission_classes = [CustomIsAdmin & CustomTokenHasReadWriteScope]
def get_queryset(self):
diff --git a/cookbook/views/delete.py b/cookbook/views/delete.py
index bbdc98ee..f37d8a55 100644
--- a/cookbook/views/delete.py
+++ b/cookbook/views/delete.py
@@ -9,7 +9,7 @@ from django.views.generic import DeleteView
from cookbook.helper.permission_helper import GroupRequiredMixin, OwnerRequiredMixin, group_required
from cookbook.models import (Comment, InviteLink, MealPlan, Recipe, RecipeBook, RecipeBookEntry,
- RecipeImport, Space, Storage, Sync, UserSpace, HomeAssistantConfig, ExampleConfig)
+ RecipeImport, Space, Storage, Sync, UserSpace, ConnectorConfig)
from cookbook.provider.dropbox import Dropbox
from cookbook.provider.local import Local
from cookbook.provider.nextcloud import Nextcloud
@@ -122,27 +122,15 @@ class StorageDelete(GroupRequiredMixin, DeleteView):
return HttpResponseRedirect(reverse('list_storage'))
-class HomeAssistantConfigDelete(GroupRequiredMixin, DeleteView):
+class ConnectorConfigDelete(GroupRequiredMixin, DeleteView):
groups_required = ['admin']
template_name = "generic/delete_template.html"
- model = HomeAssistantConfig
- success_url = reverse_lazy('list_connectors')
+ model = ConnectorConfig
+ success_url = reverse_lazy('list_connector_config')
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
- context['title'] = _("HomeAssistant Config Backend")
- return context
-
-
-class ExampleConfigDelete(GroupRequiredMixin, DeleteView):
- groups_required = ['admin']
- template_name = "generic/delete_template.html"
- model = ExampleConfig
- success_url = reverse_lazy('list_connectors')
-
- def get_context_data(self, **kwargs):
- context = super().get_context_data(**kwargs)
- context['title'] = _("Example Config Backend")
+ context['title'] = _("Connectors Config Backend")
return context
diff --git a/cookbook/views/edit.py b/cookbook/views/edit.py
index 2f99216b..356191ec 100644
--- a/cookbook/views/edit.py
+++ b/cookbook/views/edit.py
@@ -9,10 +9,10 @@ from django.utils.translation import gettext as _
from django.views.generic import UpdateView
from django.views.generic.edit import FormMixin
-from cookbook.forms import CommentForm, ExternalRecipeForm, StorageForm, SyncForm, HomeAssistantConfigForm, ExampleConfigForm
+from cookbook.forms import CommentForm, ExternalRecipeForm, StorageForm, SyncForm, ConnectorConfigForm
from cookbook.helper.permission_helper import (GroupRequiredMixin, OwnerRequiredMixin,
above_space_limit, group_required)
-from cookbook.models import Comment, Recipe, RecipeImport, Storage, Sync, HomeAssistantConfig, ExampleConfig
+from cookbook.models import Comment, Recipe, RecipeImport, Storage, Sync, ConnectorConfig
from cookbook.provider.dropbox import Dropbox
from cookbook.provider.local import Local
from cookbook.provider.nextcloud import Nextcloud
@@ -128,11 +128,11 @@ def edit_storage(request, pk):
)
-class HomeAssistantConfigUpdate(GroupRequiredMixin, UpdateView):
+class ConnectorConfigUpdate(GroupRequiredMixin, UpdateView):
groups_required = ['admin']
template_name = "generic/edit_template.html"
- model = HomeAssistantConfig
- form_class = HomeAssistantConfigForm
+ model = ConnectorConfig
+ form_class = ConnectorConfigForm
def get_form_kwargs(self):
kwargs = super().get_form_kwargs()
@@ -143,33 +143,14 @@ class HomeAssistantConfigUpdate(GroupRequiredMixin, UpdateView):
if form.cleaned_data['update_token'] != VALUE_NOT_CHANGED and form.cleaned_data['update_token'] != "":
form.instance.token = form.cleaned_data['update_token']
messages.add_message(self.request, messages.SUCCESS, _('Config saved!'))
- return super(HomeAssistantConfigUpdate, self).form_valid(form)
+ return super(ConnectorConfigUpdate, self).form_valid(form)
def get_success_url(self):
- return reverse('edit_home_assistant_config', kwargs={'pk': self.object.pk})
+ return reverse('edit_connector_config', kwargs={'pk': self.object.pk})
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
- context['title'] = _("HomeAssistantConfig")
- return context
-
-
-class ExampleConfigUpdate(GroupRequiredMixin, UpdateView):
- groups_required = ['admin']
- template_name = "generic/edit_template.html"
- model = ExampleConfig
- form_class = ExampleConfigForm
-
- def form_valid(self, form):
- messages.add_message(self.request, messages.SUCCESS, _('Config saved!'))
- return super().form_valid(form)
-
- def get_success_url(self):
- return reverse('edit_example_config', kwargs={'pk': self.object.pk})
-
- def get_context_data(self, **kwargs):
- context = super().get_context_data(**kwargs)
- context['title'] = _("ExampleConfig")
+ context['title'] = _("ConnectorConfig")
return context
diff --git a/cookbook/views/lists.py b/cookbook/views/lists.py
index ed43c143..53b1ae79 100644
--- a/cookbook/views/lists.py
+++ b/cookbook/views/lists.py
@@ -6,8 +6,8 @@ from django.utils.translation import gettext as _
from django_tables2 import RequestConfig
from cookbook.helper.permission_helper import group_required
-from cookbook.models import InviteLink, RecipeImport, Storage, SyncLog, UserFile
-from cookbook.tables import ImportLogTable, InviteLinkTable, RecipeImportTable, StorageTable
+from cookbook.models import InviteLink, RecipeImport, Storage, SyncLog, UserFile, ConnectorConfig
+from cookbook.tables import ImportLogTable, InviteLinkTable, RecipeImportTable, StorageTable, ConnectorConfigTable
@group_required('admin')
@@ -65,6 +65,22 @@ def storage(request):
)
+@group_required('admin')
+def connector_config(request):
+ table = ConnectorConfigTable(ConnectorConfig.objects.filter(space=request.space).all())
+ RequestConfig(request, paginate={'per_page': 25}).configure(table)
+
+ return render(
+ request,
+ 'generic/list_template.html',
+ {
+ 'title': _("Connector Config Backend"),
+ 'table': table,
+ 'create_url': 'new_connector_config'
+ }
+ )
+
+
@group_required('admin')
def invite_link(request):
table = InviteLinkTable(
diff --git a/cookbook/views/new.py b/cookbook/views/new.py
index 22bfbc84..93ad9996 100644
--- a/cookbook/views/new.py
+++ b/cookbook/views/new.py
@@ -5,9 +5,9 @@ from django.urls import reverse, reverse_lazy
from django.utils.translation import gettext as _
from django.views.generic import CreateView
-from cookbook.forms import ImportRecipeForm, Storage, StorageForm, HomeAssistantConfigForm, ExampleConfigForm
+from cookbook.forms import ImportRecipeForm, Storage, StorageForm, ConnectorConfigForm
from cookbook.helper.permission_helper import GroupRequiredMixin, above_space_limit, group_required
-from cookbook.models import Recipe, RecipeImport, ShareLink, Step, HomeAssistantConfig, ExampleConfig
+from cookbook.models import Recipe, RecipeImport, ShareLink, Step, ConnectorConfig
from recipes import settings
@@ -70,21 +70,12 @@ class StorageCreate(GroupRequiredMixin, CreateView):
return context
-class HomeAssistantConfigCreate(GroupRequiredMixin, CreateView):
+class ConnectorConfigCreate(GroupRequiredMixin, CreateView):
groups_required = ['admin']
template_name = "generic/new_template.html"
- model = HomeAssistantConfig
- form_class = HomeAssistantConfigForm
- success_url = reverse_lazy('list_home_assistant_config')
-
- def get_form_class(self):
- form_class = super().get_form_class()
-
- if self.request.method == 'GET':
- update_token_field = form_class.base_fields['update_token']
- update_token_field.required = True
-
- return form_class
+ model = ConnectorConfig
+ form_class = ConnectorConfigForm
+ success_url = reverse_lazy('list_connector_config')
def form_valid(self, form):
if self.request.space.demo or settings.HOSTED:
@@ -96,35 +87,11 @@ class HomeAssistantConfigCreate(GroupRequiredMixin, CreateView):
obj.created_by = self.request.user
obj.space = self.request.space
obj.save()
- return HttpResponseRedirect(reverse('edit_home_assistant_config', kwargs={'pk': obj.pk}))
+ return HttpResponseRedirect(reverse('edit_connector_config', kwargs={'pk': obj.pk}))
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
- context['title'] = _("HomeAssistant Config Backend")
- return context
-
-
-class ExampleConfigCreate(GroupRequiredMixin, CreateView):
- groups_required = ['admin']
- template_name = "generic/new_template.html"
- model = ExampleConfig
- form_class = ExampleConfigForm
- success_url = reverse_lazy('list_connectors')
-
- def form_valid(self, form):
- if self.request.space.demo or settings.HOSTED:
- messages.add_message(self.request, messages.ERROR, _('This feature is not yet available in the hosted version of tandoor!'))
- return redirect('index')
-
- obj = form.save(commit=False)
- obj.created_by = self.request.user
- obj.space = self.request.space
- obj.save()
- return HttpResponseRedirect(reverse('edit_example_config', kwargs={'pk': obj.pk}))
-
- def get_context_data(self, **kwargs):
- context = super().get_context_data(**kwargs)
- context['title'] = _("Example Config Backend")
+ context['title'] = _("Connector Config Backend")
return context