From 408399fec7a16791635f9893bdd5bd669e976fb6 Mon Sep 17 00:00:00 2001 From: jade <101148768+jadeddelta@users.noreply.github.com> Date: Thu, 11 Sep 2025 14:40:49 -0400 Subject: [PATCH 1/8] initial implementation of redirect service --- addon_imps/redirect/__init__.py | 2 ++ addon_imps/redirect/datapipe.py | 0 addon_service/admin/__init__.py | 19 ++++++++++ .../external_service/redirect/__init__.py | 2 ++ .../external_service/redirect/models.py | 14 ++++++++ .../external_service/redirect/serializers.py | 36 +++++++++++++++++++ .../external_service/redirect/views.py | 21 +++++++++++ addon_service/external_service/serializers.py | 6 ++-- .../0017_externalredirectservice.py | 26 ++++++++++++++ addon_service/models.py | 2 ++ addon_service/urls.py | 1 + addon_service/views.py | 2 ++ addon_toolkit/interfaces/redirect.py | 4 +++ 13 files changed, 132 insertions(+), 3 deletions(-) create mode 100644 addon_imps/redirect/__init__.py create mode 100644 addon_imps/redirect/datapipe.py create mode 100644 addon_service/external_service/redirect/__init__.py create mode 100644 addon_service/external_service/redirect/models.py create mode 100644 addon_service/external_service/redirect/serializers.py create mode 100644 addon_service/external_service/redirect/views.py create mode 100644 addon_service/migrations/0017_externalredirectservice.py create mode 100644 addon_toolkit/interfaces/redirect.py diff --git a/addon_imps/redirect/__init__.py b/addon_imps/redirect/__init__.py new file mode 100644 index 00000000..a2f4aa93 --- /dev/null +++ b/addon_imps/redirect/__init__.py @@ -0,0 +1,2 @@ +"""addon_imps.redirect: imps that implement a redirect-like interface +""" diff --git a/addon_imps/redirect/datapipe.py b/addon_imps/redirect/datapipe.py new file mode 100644 index 00000000..e69de29b diff --git a/addon_service/admin/__init__.py b/addon_service/admin/__init__.py index 10389538..6e3fc930 100644 --- a/addon_service/admin/__init__.py +++ b/addon_service/admin/__init__.py @@ -92,6 +92,25 @@ class ExternalComputingServiceAdmin(GravyvaletModelAdmin): "int_supported_features": ComputingSupportedFeatures, } +@admin.register(models.ExternalRedirectService) +class ExternalRedirectServiceAdmin(GravyvaletModelAdmin): + list_display = ("display_name", "created", "modified") + readonly_fields = ( + "id", + "created", + "modified", + ) + raw_id_fields = ("oauth2_client_config", "oauth1_client_config") + enum_choice_fields = { + "int_addon_imp": known_imps.StorageAddonImpNumbers, + "int_credentials_format": CredentialsFormats, + "int_service_type": ServiceTypes, + } + enum_multiple_choice_fields = { + "int_supported_features": StorageSupportedFeatures, + } + + @admin.register(models.OAuth2ClientConfig) @linked_many_field("external_storage_services") diff --git a/addon_service/external_service/redirect/__init__.py b/addon_service/external_service/redirect/__init__.py new file mode 100644 index 00000000..5eb73c4d --- /dev/null +++ b/addon_service/external_service/redirect/__init__.py @@ -0,0 +1,2 @@ +"""addon_service.external_service.redirect: imps that implement a redirect service +""" \ No newline at end of file diff --git a/addon_service/external_service/redirect/models.py b/addon_service/external_service/redirect/models.py new file mode 100644 index 00000000..a5f9a549 --- /dev/null +++ b/addon_service/external_service/redirect/models.py @@ -0,0 +1,14 @@ +from django.db import models + +from addon_service.external_service.models import ExternalService + +class ExternalRedirectService(ExternalService): + redirect_url = models.URLField(blank=True, default="") + + class Meta: + verbose_name = "External Redirect Service" + verbose_name_plural = "External Redirect Services" + app_label = "addon_service" + + class JSONAPIMeta: + resource_name = "external-redirect-services" diff --git a/addon_service/external_service/redirect/serializers.py b/addon_service/external_service/redirect/serializers.py new file mode 100644 index 00000000..12596336 --- /dev/null +++ b/addon_service/external_service/redirect/serializers.py @@ -0,0 +1,36 @@ +from rest_framework_json_api import serializers +from rest_framework_json_api.utils import get_resource_type_from_model + +from addon_service.common import view_names + +from addon_service.external_service.serializers import ExternalServiceSerializer + +from .models import ExternalRedirectService + + +RESOURCE_TYPE = get_resource_type_from_model(ExternalRedirectService) + + +class ExternalRedirectServiceSerializer(ExternalServiceSerializer): + """api serializer for the `ExternalRedirectService` model""" + + redirect_url = serializers.CharField( + read_only=True, + ) + + class Meta: + model = ExternalRedirectService + fields = [ + "id", + "addon_imp", + "auth_uri", + "credentials_format", + "display_name", + "url", + "wb_key", + "external_service_name", + "configurable_api_root", + "icon_url", + "api_base_url_options", + "redirect_url", + ] diff --git a/addon_service/external_service/redirect/views.py b/addon_service/external_service/redirect/views.py new file mode 100644 index 00000000..e9377613 --- /dev/null +++ b/addon_service/external_service/redirect/views.py @@ -0,0 +1,21 @@ +from drf_spectacular.utils import ( + extend_schema, + extend_schema_view, +) +from rest_framework_json_api.views import ReadOnlyModelViewSet + +from .models import ExternalRedirectService +from .serializers import ExternalRedirectServiceSerializer + + +@extend_schema_view( + list=extend_schema( + description="Get the list of all available external redirect services" + ), + get=extend_schema( + description="Get particular external redirect service", + ), +) +class ExternalRedirectServiceViewSet(ReadOnlyModelViewSet): + queryset = ExternalRedirectService.objects.all().select_related("oauth2_client_config") + serializer_class = ExternalRedirectServiceSerializer diff --git a/addon_service/external_service/serializers.py b/addon_service/external_service/serializers.py index f824774f..d96d761e 100644 --- a/addon_service/external_service/serializers.py +++ b/addon_service/external_service/serializers.py @@ -36,9 +36,9 @@ def get_icon_url(self, obj: ExternalService): child=serializers.CharField(), read_only=True ) - included_serializers = { - "addon_imp": "addon_service.serializers.AddonImpSerializer", - } + # included_serializers = { + # "addon_imp": "addon_service.serializers.AddonImpSerializer", + # } class Meta: model = ExternalService diff --git a/addon_service/migrations/0017_externalredirectservice.py b/addon_service/migrations/0017_externalredirectservice.py new file mode 100644 index 00000000..daad0640 --- /dev/null +++ b/addon_service/migrations/0017_externalredirectservice.py @@ -0,0 +1,26 @@ +# Generated by Django 4.2.20 on 2025-09-11 18:14 + +from django.db import migrations, models +import django.db.models.deletion + + +class Migration(migrations.Migration): + + dependencies = [ + ('addon_service', '0016_externallinkservice_int_supported_features_and_more'), + ] + + operations = [ + migrations.CreateModel( + name='ExternalRedirectService', + fields=[ + ('externalservice_ptr', models.OneToOneField(auto_created=True, on_delete=django.db.models.deletion.CASCADE, parent_link=True, primary_key=True, serialize=False, to='addon_service.externalservice')), + ('redirect_url', models.URLField(blank=True, default='')), + ], + options={ + 'verbose_name': 'External Redirect Service', + 'verbose_name_plural': 'External Redirect Services', + }, + bases=('addon_service.externalservice',), + ), + ] diff --git a/addon_service/models.py b/addon_service/models.py index b350e261..e1fa62e8 100644 --- a/addon_service/models.py +++ b/addon_service/models.py @@ -16,6 +16,7 @@ from addon_service.external_service.computing.models import ExternalComputingService from addon_service.external_service.link.models import ExternalLinkService from addon_service.external_service.storage.models import ExternalStorageService +from addon_service.external_service.redirect.models import ExternalRedirectService from addon_service.oauth1.models import OAuth1ClientConfig from addon_service.oauth2.models import ( OAuth2ClientConfig, @@ -47,4 +48,5 @@ "ExternalLinkService", "AuthorizedLinkAccount", "ConfiguredLinkAddon", + "ExternalRedirectService", ) diff --git a/addon_service/urls.py b/addon_service/urls.py index ab89009d..adaa482f 100644 --- a/addon_service/urls.py +++ b/addon_service/urls.py @@ -68,6 +68,7 @@ def _register_viewset(viewset): _register_viewset(views.AddonOperationViewSet) _register_viewset(views.AddonImpViewSet) _register_viewset(views.UserReferenceViewSet) +_register_viewset(views.ExternalRedirectServiceViewSet) ### diff --git a/addon_service/views.py b/addon_service/views.py index f72ee948..7996234e 100644 --- a/addon_service/views.py +++ b/addon_service/views.py @@ -31,6 +31,7 @@ ExternalComputingServiceViewSet, ) from addon_service.external_service.link.views import ExternalLinkServiceViewSet +from addon_service.external_service.redirect.views import ExternalRedirectServiceViewSet from addon_service.external_service.storage.views import ExternalStorageServiceViewSet from addon_service.oauth1.views import oauth1_callback_view from addon_service.oauth2.views import oauth2_callback_view @@ -70,6 +71,7 @@ async def status(request): "AuthorizedStorageAccountViewSet", "ConfiguredStorageAddonViewSet", "ExternalStorageServiceViewSet", + "ExternalRedirectServiceViewSet", "ResourceReferenceViewSet", "UserReferenceViewSet", "oauth2_callback_view", diff --git a/addon_toolkit/interfaces/redirect.py b/addon_toolkit/interfaces/redirect.py new file mode 100644 index 00000000..7017caef --- /dev/null +++ b/addon_toolkit/interfaces/redirect.py @@ -0,0 +1,4 @@ +"""a static (and still in progress) definition of what composes a redirect addon""" + +import dataclasses +import typing \ No newline at end of file From 5e18636e8e51fcff1aaa6c1e2616119900460cf5 Mon Sep 17 00:00:00 2001 From: Yuhuai Liu Date: Sat, 13 Sep 2025 22:43:05 -0400 Subject: [PATCH 2/8] fix some issues so that API returns external redirect service --- .../external_service/redirect/serializers.py | 12 +++++++++++- addon_service/external_service/redirect/views.py | 2 +- addon_service/external_service/serializers.py | 6 +++--- 3 files changed, 15 insertions(+), 5 deletions(-) diff --git a/addon_service/external_service/redirect/serializers.py b/addon_service/external_service/redirect/serializers.py index 12596336..3cf8308c 100644 --- a/addon_service/external_service/redirect/serializers.py +++ b/addon_service/external_service/redirect/serializers.py @@ -1,8 +1,9 @@ from rest_framework_json_api import serializers from rest_framework_json_api.utils import get_resource_type_from_model +from addon_service.addon_imp.models import AddonImpModel from addon_service.common import view_names - +from addon_service.common.serializer_fields import DataclassRelatedDataField from addon_service.external_service.serializers import ExternalServiceSerializer from .models import ExternalRedirectService @@ -14,6 +15,15 @@ class ExternalRedirectServiceSerializer(ExternalServiceSerializer): """api serializer for the `ExternalRedirectService` model""" + url = serializers.HyperlinkedIdentityField( + view_name=view_names.detail_view(RESOURCE_TYPE) + ) + + addon_imp = DataclassRelatedDataField( + dataclass_model=AddonImpModel, + related_link_view_name=view_names.related_view(RESOURCE_TYPE), + ) + redirect_url = serializers.CharField( read_only=True, ) diff --git a/addon_service/external_service/redirect/views.py b/addon_service/external_service/redirect/views.py index e9377613..e2e106d0 100644 --- a/addon_service/external_service/redirect/views.py +++ b/addon_service/external_service/redirect/views.py @@ -17,5 +17,5 @@ ), ) class ExternalRedirectServiceViewSet(ReadOnlyModelViewSet): - queryset = ExternalRedirectService.objects.all().select_related("oauth2_client_config") + queryset = ExternalRedirectService.objects.all() serializer_class = ExternalRedirectServiceSerializer diff --git a/addon_service/external_service/serializers.py b/addon_service/external_service/serializers.py index d96d761e..f824774f 100644 --- a/addon_service/external_service/serializers.py +++ b/addon_service/external_service/serializers.py @@ -36,9 +36,9 @@ def get_icon_url(self, obj: ExternalService): child=serializers.CharField(), read_only=True ) - # included_serializers = { - # "addon_imp": "addon_service.serializers.AddonImpSerializer", - # } + included_serializers = { + "addon_imp": "addon_service.serializers.AddonImpSerializer", + } class Meta: model = ExternalService From befce0a3e79c9468341b1a307b2e55c8c21a920c Mon Sep 17 00:00:00 2001 From: jade <101148768+jadeddelta@users.noreply.github.com> Date: Thu, 18 Sep 2025 13:44:26 -0400 Subject: [PATCH 3/8] register redirect service as an AddonImp --- addon_imps/redirect/datapipe.py | 0 addon_imps/redirect/redirect_dummy.py | 7 +++++++ addon_service/admin/__init__.py | 2 +- addon_service/common/known_imps.py | 9 +++++++++ addon_toolkit/interfaces/redirect.py | 8 +++++++- 5 files changed, 24 insertions(+), 2 deletions(-) delete mode 100644 addon_imps/redirect/datapipe.py create mode 100644 addon_imps/redirect/redirect_dummy.py diff --git a/addon_imps/redirect/datapipe.py b/addon_imps/redirect/datapipe.py deleted file mode 100644 index e69de29b..00000000 diff --git a/addon_imps/redirect/redirect_dummy.py b/addon_imps/redirect/redirect_dummy.py new file mode 100644 index 00000000..5fdaa989 --- /dev/null +++ b/addon_imps/redirect/redirect_dummy.py @@ -0,0 +1,7 @@ +from addon_toolkit.imp import AddonImp + + +class DummyRedirectImp(AddonImp): + """this is a dummy AddonImp for ALL redirect services. + redirect links will be specified in django admin configuration.""" + pass \ No newline at end of file diff --git a/addon_service/admin/__init__.py b/addon_service/admin/__init__.py index 6e3fc930..dfc0c02f 100644 --- a/addon_service/admin/__init__.py +++ b/addon_service/admin/__init__.py @@ -102,7 +102,7 @@ class ExternalRedirectServiceAdmin(GravyvaletModelAdmin): ) raw_id_fields = ("oauth2_client_config", "oauth1_client_config") enum_choice_fields = { - "int_addon_imp": known_imps.StorageAddonImpNumbers, + "int_addon_imp": known_imps.RedirectAddonImpNumbers, "int_credentials_format": CredentialsFormats, "int_service_type": ServiceTypes, } diff --git a/addon_service/common/known_imps.py b/addon_service/common/known_imps.py index 8692e99e..744b2db4 100644 --- a/addon_service/common/known_imps.py +++ b/addon_service/common/known_imps.py @@ -24,12 +24,14 @@ owncloud, s3, ) +from addon_imps.redirect import redirect_dummy from addon_service.common.enum_decorators import enum_names_same_as from addon_toolkit import AddonImp from addon_toolkit.interfaces.citation import CitationAddonImp from addon_toolkit.interfaces.computing import ComputingAddonImp from addon_toolkit.interfaces.link import LinkAddonImp from addon_toolkit.interfaces.storage import StorageAddonImp +from addon_toolkit.interfaces.redirect import RedirectAddonImp if __debug__: @@ -100,6 +102,9 @@ class KnownAddonImps(enum.Enum): # Type: Link LINK_DATAVERSE = link_dataverse.DataverseLinkImp + # Type: Redirect + REDIRECT_DUMMY = redirect_dummy.DummyRedirectImp + if __debug__: BLARG = my_blarg.MyBlargStorage @@ -137,6 +142,9 @@ class AddonImpNumbers(enum.Enum): # Type: Link LINK_DATAVERSE = 1030 + # Type: Redirect + REDIRECT_DUMMY = 1040 + if __debug__: BLARG = -7 @@ -155,3 +163,4 @@ def filter_addons_by_type(addon_type): CitationAddonImpNumbers = filter_addons_by_type(CitationAddonImp) ComputingAddonImpNumbers = filter_addons_by_type(ComputingAddonImp) LinkAddonImpNumbers = filter_addons_by_type(LinkAddonImp) +RedirectAddonImpNumbers = filter_addons_by_type(RedirectAddonImp) diff --git a/addon_toolkit/interfaces/redirect.py b/addon_toolkit/interfaces/redirect.py index 7017caef..8b7edc7d 100644 --- a/addon_toolkit/interfaces/redirect.py +++ b/addon_toolkit/interfaces/redirect.py @@ -1,4 +1,10 @@ """a static (and still in progress) definition of what composes a redirect addon""" import dataclasses -import typing \ No newline at end of file + +from addon_toolkit.imp import AddonImp + +@dataclasses.dataclass +class RedirectAddonImp(AddonImp): + """base class for redirect addon implementations""" + pass \ No newline at end of file From d389804599aa5b8512f88af84611bf60aeb452f7 Mon Sep 17 00:00:00 2001 From: futa-ikeda Date: Thu, 16 Oct 2025 12:08:49 -0400 Subject: [PATCH 4/8] Appease pre-commit linters --- addon_imps/redirect/redirect_dummy.py | 5 ++-- addon_service/admin/__init__.py | 2 +- addon_service/common/known_imps.py | 6 ++-- .../external_service/redirect/__init__.py | 2 +- .../external_service/redirect/models.py | 1 + .../0017_externalredirectservice.py | 29 ++++++++++++++----- addon_service/models.py | 2 +- addon_toolkit/interfaces/redirect.py | 4 ++- 8 files changed, 34 insertions(+), 17 deletions(-) diff --git a/addon_imps/redirect/redirect_dummy.py b/addon_imps/redirect/redirect_dummy.py index 5fdaa989..a9f5f73b 100644 --- a/addon_imps/redirect/redirect_dummy.py +++ b/addon_imps/redirect/redirect_dummy.py @@ -2,6 +2,7 @@ class DummyRedirectImp(AddonImp): - """this is a dummy AddonImp for ALL redirect services. + """this is a dummy AddonImp for ALL redirect services. redirect links will be specified in django admin configuration.""" - pass \ No newline at end of file + + pass diff --git a/addon_service/admin/__init__.py b/addon_service/admin/__init__.py index dfc0c02f..47d8dab2 100644 --- a/addon_service/admin/__init__.py +++ b/addon_service/admin/__init__.py @@ -92,6 +92,7 @@ class ExternalComputingServiceAdmin(GravyvaletModelAdmin): "int_supported_features": ComputingSupportedFeatures, } + @admin.register(models.ExternalRedirectService) class ExternalRedirectServiceAdmin(GravyvaletModelAdmin): list_display = ("display_name", "created", "modified") @@ -111,7 +112,6 @@ class ExternalRedirectServiceAdmin(GravyvaletModelAdmin): } - @admin.register(models.OAuth2ClientConfig) @linked_many_field("external_storage_services") @linked_many_field("external_citation_services") diff --git a/addon_service/common/known_imps.py b/addon_service/common/known_imps.py index 744b2db4..876b8e7e 100644 --- a/addon_service/common/known_imps.py +++ b/addon_service/common/known_imps.py @@ -11,6 +11,7 @@ ) from addon_imps.computing import boa from addon_imps.link import dataverse as link_dataverse +from addon_imps.redirect import redirect_dummy from addon_imps.storage import ( bitbucket, box_dot_com, @@ -24,14 +25,13 @@ owncloud, s3, ) -from addon_imps.redirect import redirect_dummy from addon_service.common.enum_decorators import enum_names_same_as from addon_toolkit import AddonImp from addon_toolkit.interfaces.citation import CitationAddonImp from addon_toolkit.interfaces.computing import ComputingAddonImp from addon_toolkit.interfaces.link import LinkAddonImp -from addon_toolkit.interfaces.storage import StorageAddonImp from addon_toolkit.interfaces.redirect import RedirectAddonImp +from addon_toolkit.interfaces.storage import StorageAddonImp if __debug__: @@ -142,7 +142,7 @@ class AddonImpNumbers(enum.Enum): # Type: Link LINK_DATAVERSE = 1030 - # Type: Redirect + # Type: Redirect REDIRECT_DUMMY = 1040 if __debug__: diff --git a/addon_service/external_service/redirect/__init__.py b/addon_service/external_service/redirect/__init__.py index 5eb73c4d..713236df 100644 --- a/addon_service/external_service/redirect/__init__.py +++ b/addon_service/external_service/redirect/__init__.py @@ -1,2 +1,2 @@ """addon_service.external_service.redirect: imps that implement a redirect service -""" \ No newline at end of file +""" diff --git a/addon_service/external_service/redirect/models.py b/addon_service/external_service/redirect/models.py index a5f9a549..12b1becb 100644 --- a/addon_service/external_service/redirect/models.py +++ b/addon_service/external_service/redirect/models.py @@ -2,6 +2,7 @@ from addon_service.external_service.models import ExternalService + class ExternalRedirectService(ExternalService): redirect_url = models.URLField(blank=True, default="") diff --git a/addon_service/migrations/0017_externalredirectservice.py b/addon_service/migrations/0017_externalredirectservice.py index daad0640..8620939a 100644 --- a/addon_service/migrations/0017_externalredirectservice.py +++ b/addon_service/migrations/0017_externalredirectservice.py @@ -1,26 +1,39 @@ # Generated by Django 4.2.20 on 2025-09-11 18:14 -from django.db import migrations, models import django.db.models.deletion +from django.db import ( + migrations, + models, +) class Migration(migrations.Migration): dependencies = [ - ('addon_service', '0016_externallinkservice_int_supported_features_and_more'), + ("addon_service", "0016_externallinkservice_int_supported_features_and_more"), ] operations = [ migrations.CreateModel( - name='ExternalRedirectService', + name="ExternalRedirectService", fields=[ - ('externalservice_ptr', models.OneToOneField(auto_created=True, on_delete=django.db.models.deletion.CASCADE, parent_link=True, primary_key=True, serialize=False, to='addon_service.externalservice')), - ('redirect_url', models.URLField(blank=True, default='')), + ( + "externalservice_ptr", + models.OneToOneField( + auto_created=True, + on_delete=django.db.models.deletion.CASCADE, + parent_link=True, + primary_key=True, + serialize=False, + to="addon_service.externalservice", + ), + ), + ("redirect_url", models.URLField(blank=True, default="")), ], options={ - 'verbose_name': 'External Redirect Service', - 'verbose_name_plural': 'External Redirect Services', + "verbose_name": "External Redirect Service", + "verbose_name_plural": "External Redirect Services", }, - bases=('addon_service.externalservice',), + bases=("addon_service.externalservice",), ), ] diff --git a/addon_service/models.py b/addon_service/models.py index e1fa62e8..f381f961 100644 --- a/addon_service/models.py +++ b/addon_service/models.py @@ -15,8 +15,8 @@ from addon_service.external_service.citation.models import ExternalCitationService from addon_service.external_service.computing.models import ExternalComputingService from addon_service.external_service.link.models import ExternalLinkService -from addon_service.external_service.storage.models import ExternalStorageService from addon_service.external_service.redirect.models import ExternalRedirectService +from addon_service.external_service.storage.models import ExternalStorageService from addon_service.oauth1.models import OAuth1ClientConfig from addon_service.oauth2.models import ( OAuth2ClientConfig, diff --git a/addon_toolkit/interfaces/redirect.py b/addon_toolkit/interfaces/redirect.py index 8b7edc7d..4ca3c5fc 100644 --- a/addon_toolkit/interfaces/redirect.py +++ b/addon_toolkit/interfaces/redirect.py @@ -4,7 +4,9 @@ from addon_toolkit.imp import AddonImp + @dataclasses.dataclass class RedirectAddonImp(AddonImp): """base class for redirect addon implementations""" - pass \ No newline at end of file + + pass From 664673d96d467d4a0e0274c3bb58f4b93958512a Mon Sep 17 00:00:00 2001 From: futa-ikeda Date: Thu, 16 Oct 2025 13:14:24 -0400 Subject: [PATCH 5/8] Update DummyRedirectImp --- addon_imps/redirect/redirect_dummy.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/addon_imps/redirect/redirect_dummy.py b/addon_imps/redirect/redirect_dummy.py index a9f5f73b..ec841b5e 100644 --- a/addon_imps/redirect/redirect_dummy.py +++ b/addon_imps/redirect/redirect_dummy.py @@ -1,8 +1,9 @@ from addon_toolkit.imp import AddonImp +from addon_toolkit.interfaces._base import BaseAddonInterface class DummyRedirectImp(AddonImp): """this is a dummy AddonImp for ALL redirect services. redirect links will be specified in django admin configuration.""" - pass + ADDON_INTERFACE = BaseAddonInterface From a9ae73c13cacf381f92b4ca94226966a767c2b9a Mon Sep 17 00:00:00 2001 From: futa-ikeda Date: Fri, 17 Oct 2025 10:16:46 -0400 Subject: [PATCH 6/8] Allow selection of Addon Implementation in admin app --- addon_imps/redirect/redirect_dummy.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/addon_imps/redirect/redirect_dummy.py b/addon_imps/redirect/redirect_dummy.py index ec841b5e..53c3f3b8 100644 --- a/addon_imps/redirect/redirect_dummy.py +++ b/addon_imps/redirect/redirect_dummy.py @@ -1,8 +1,8 @@ -from addon_toolkit.imp import AddonImp from addon_toolkit.interfaces._base import BaseAddonInterface +from addon_toolkit.interfaces.redirect import RedirectAddonImp -class DummyRedirectImp(AddonImp): +class DummyRedirectImp(RedirectAddonImp): """this is a dummy AddonImp for ALL redirect services. redirect links will be specified in django admin configuration.""" From 911025c9733083669133252211c3698cbde97d57 Mon Sep 17 00:00:00 2001 From: futa-ikeda Date: Fri, 17 Oct 2025 10:17:01 -0400 Subject: [PATCH 7/8] Add Datapipe logo --- .../static/provider_icons/datapipe.png | Bin 0 -> 38317 bytes 1 file changed, 0 insertions(+), 0 deletions(-) create mode 100644 addon_service/static/provider_icons/datapipe.png diff --git a/addon_service/static/provider_icons/datapipe.png b/addon_service/static/provider_icons/datapipe.png new file mode 100644 index 0000000000000000000000000000000000000000..2272662a577ff768343c7351dfe2846d22b8626c GIT binary patch literal 38317 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 Date: Fri, 2 Jan 2026 11:23:29 -0500 Subject: [PATCH 8/8] CR feedback --- addon_service/admin/__init__.py | 3 --- 1 file changed, 3 deletions(-) diff --git a/addon_service/admin/__init__.py b/addon_service/admin/__init__.py index 47d8dab2..da1bfad0 100644 --- a/addon_service/admin/__init__.py +++ b/addon_service/admin/__init__.py @@ -107,9 +107,6 @@ class ExternalRedirectServiceAdmin(GravyvaletModelAdmin): "int_credentials_format": CredentialsFormats, "int_service_type": ServiceTypes, } - enum_multiple_choice_fields = { - "int_supported_features": StorageSupportedFeatures, - } @admin.register(models.OAuth2ClientConfig)