diff --git a/djangocms_link/admin.py b/djangocms_link/admin.py index 9b26465b..c3ac60ed 100644 --- a/djangocms_link/admin.py +++ b/djangocms_link/admin.py @@ -1,6 +1,8 @@ from django.apps import apps +from django.conf import settings from django.contrib import admin from django.core.exceptions import FieldError, PermissionDenied +from django.db.models import Q from django.http import JsonResponse from django.urls import path from django.views.generic.list import BaseListView @@ -22,13 +24,7 @@ class GrouperModelAdmin: pass -REGISTERED_ADMIN = [] - -for _admin in admin.site._registry.values(): - if _admin.model._meta.app_label == "cms": - continue - if getattr(_admin, "search_fields", []) and hasattr(_admin.model, "get_absolute_url"): - REGISTERED_ADMIN.append(_admin) +REGISTERED_ADMIN = getattr(settings, "DJANGOCMS_LINK_URL_ADMINS", "auto") class AdminUrlsView(BaseListView): @@ -133,12 +129,19 @@ def get_queryset(self): return list(qs) def add_admin_querysets(self, qs): + if REGISTERED_ADMIN == "auto": + return qs + for model_admin in REGISTERED_ADMIN: try: # hack: GrouperModelAdmin expects a language to be temporarily set if isinstance(model_admin, GrouperModelAdmin): model_admin.language = self.language new_qs = model_admin.get_queryset(self.request) + if hasattr(model_admin.model, "site") and self.site: + new_qs = new_qs.filter(Q(site_id=self.site) | Q(site__isnull=True)) + elif hasattr(model_admin.model, "sites") and self.site: + new_qs = new_qs.filter(sites__id=self.site) new_qs, search_use_distinct = model_admin.get_search_results( self.request, new_qs, self.term ) diff --git a/djangocms_link/apps.py b/djangocms_link/apps.py index 68722e7d..c0eac622 100644 --- a/djangocms_link/apps.py +++ b/djangocms_link/apps.py @@ -5,3 +5,22 @@ class DjangoCmsLinkConfig(AppConfig): name = "djangocms_link" verbose_name = _("django CMS Link") + + def ready(self): + # Only scan admins after all apps are loaded + + from django.contrib import admin + + from djangocms_link import admin as link_admin + + if link_admin.REGISTERED_ADMIN == "auto": + # Autoconfig? Check the admin registry for suitable admins + link_admin.REGISTERED_ADMIN = [] + for _admin in admin.site._registry.values(): + if _admin.model._meta.app_label == "cms": + # Skip CMS models + continue + # search_fields need to be defined in the ModelAdmin class, and the model needs to have + # a get_absolute_url method. + if getattr(_admin, "search_fields", []) and hasattr(_admin.model, "get_absolute_url"): + link_admin.REGISTERED_ADMIN.append(_admin) diff --git a/tests/test_endpoint.py b/tests/test_endpoint.py index ccc05d04..54e39ac7 100644 --- a/tests/test_endpoint.py +++ b/tests/test_endpoint.py @@ -1,12 +1,16 @@ +from django.contrib import admin +from django.contrib.sites.models import Site + from cms.api import create_page from cms.models import Page from cms.test_utils.testcases import CMSTestCase from cms.utils.urlutils import admin_reverse from djangocms_link.models import Link +from tests.utils.models import ThirdPartyModel -class LinkModelTestCase(CMSTestCase): +class LinkEndpointTestCase(CMSTestCase): def setUp(self): self.root_page = create_page( title="root", @@ -40,7 +44,6 @@ def tearDown(self): self.subling.delete() def test_api_endpoint(self): - for query_params in ("", "?app_label=1"): with self.subTest(query_params=query_params): with self.login_user_context(self.get_superuser()): @@ -115,3 +118,119 @@ def test_get_reference(self): self.assertEqual(data["id"], "cms.page:1") self.assertEqual(data["text"], "root") self.assertEqual(data["url"], self.root_page.get_absolute_url()) + + +class LinkEndpointThirdPartyTestCase(CMSTestCase): + def setUp(self): + LinkAdmin = admin.site._registry[Link] + self.endpoint = admin_reverse(LinkAdmin.link_url_name) + + self.second_site = Site.objects.create( + domain="second", + name="second", + ) + + self.items = ( + ThirdPartyModel.objects.create(name="First", path="/first", site_id=1), + ThirdPartyModel.objects.create(name="Second", path="/second", site=self.second_site), + ThirdPartyModel.objects.create(name="django CMS", path="/django-cms"), + ThirdPartyModel.objects.create(name="django CMS rocks", path="/django-cms-2"), + ) + + def test_auto_config(self): + from djangocms_link.admin import REGISTERED_ADMIN + from tests.utils.admin import ThirdPartyAdmin + + for registered_admin in REGISTERED_ADMIN: + if isinstance(registered_admin, ThirdPartyAdmin): + break + else: + self.asserFail("ThirdPartyAdmin not found in REGISTERED_ADMIN") + + def test_api_endpoint(self): + for query_params in ("", "?app_label=1"): + with self.subTest(query_params=query_params): + with self.login_user_context(self.get_superuser()): + response = self.client.get(self.endpoint + query_params) + self.assertEqual(response.status_code, 200) + data = response.json() + + self.assertIn("results", data) + self.assertEqual(len(data["results"]), 1) + self.assertIn("pagination", data) + self.assertEqual(data["pagination"]["more"], False) + destinations = data["results"][0] + self.assertEqual(destinations["text"], "Third party models") + for destination in destinations["children"]: + self.assertIn("id", destination) + self.assertIn("text", destination) + self.assertIn("url", destination) + _, pk = destination["id"].split(":") + db_obj = ThirdPartyModel.objects.get(pk=pk) + self.assertEqual(destination["text"], str(db_obj)) + + def test_filter(self): + with self.login_user_context(self.get_superuser()): + response = self.client.get(self.endpoint + "?term=CMS") + self.assertEqual(response.status_code, 200) + data = response.json() + + self.assertIn("results", data) + self.assertEqual(len(data["results"]), 1) + self.assertIn("pagination", data) + self.assertEqual(data["pagination"]["more"], False) + + pages = data["results"][0] + self.assertEqual(len(pages["children"]), 2) + + def test_site_selector(self): + + for site_id in (1, 2): + with self.subTest(site_id=site_id): + with self.login_user_context(self.get_superuser()): + response = self.client.get(self.endpoint + f"?app_label={site_id}") + self.assertEqual(response.status_code, 200) + data = response.json() + + self.assertIn("results", data) + self.assertEqual(len(data["results"]), 1) + self.assertIn("pagination", data) + self.assertEqual(data["pagination"]["more"], False) + destinations = data["results"][0] + self.assertEqual(destinations["text"], "Third party models") + # One site-specific item, two all-sites items + self.assertEqual(len(destinations["children"]), 3) + + # Specific site item + if site_id == 1: + self.assertIn( + {'id': 'utils.thirdpartymodel:1', 'text': 'First', 'url': '/first'}, + destinations["children"] + ) + else: + self.assertIn( + {'id': 'utils.thirdpartymodel:2', 'text': 'Second', 'url': '/second'}, + destinations["children"] + ) + # All-sites items + self.assertIn( + {'id': 'utils.thirdpartymodel:3', 'text': 'django CMS', 'url': '/django-cms'}, + destinations["children"] + ) + self.assertIn( + {'id': 'utils.thirdpartymodel:4', 'text': 'django CMS rocks', 'url': '/django-cms-2'}, + destinations["children"] + ) + + def test_get_reference(self): + with self.login_user_context(self.get_superuser()): + response = self.client.get(self.endpoint + "?g=utils.thirdpartymodel:1") + self.assertEqual(response.status_code, 200) + data = response.json() + + self.assertIn("id", data) + self.assertIn("text", data) + self.assertIn("url", data) + self.assertEqual(data["id"], "utils.thirdpartymodel:1") + self.assertEqual(data["text"], "First") + self.assertEqual(data["url"], self.items[0].get_absolute_url()) diff --git a/tests/utils/admin.py b/tests/utils/admin.py new file mode 100644 index 00000000..c47f2252 --- /dev/null +++ b/tests/utils/admin.py @@ -0,0 +1,8 @@ +from django.contrib import admin + +from tests.utils.models import ThirdPartyModel + + +@admin.register(ThirdPartyModel) +class ThirdPartyAdmin(admin.ModelAdmin): + search_fields = ("name",) diff --git a/tests/utils/models.py b/tests/utils/models.py new file mode 100644 index 00000000..dd64143e --- /dev/null +++ b/tests/utils/models.py @@ -0,0 +1,13 @@ +from django.db import models + + +class ThirdPartyModel(models.Model): + name = models.CharField(max_length=255) + path = models.CharField(max_length=255) + site = models.ForeignKey("sites.Site", on_delete=models.SET_NULL, null=True) + + def get_absolute_url(self): + return self.path + + def __str__(self): + return self.name