2
0
Эх сурвалжийг харах

Add initial redirects (contrib) API endpoint

Builds on previous PRs #6110 & #8842
Rohit Sharma 1 жил өмнө
parent
commit
996abeae8e

+ 1 - 0
CHANGELOG.txt

@@ -50,6 +50,7 @@ Changelog
  * Add the accessibility checker within the page and snippets editor (Thibaud Colas)
  * Add `DrilldownController` and `w-drilldown` component to support drilldown menus (Thibaud Colas)
  * Add support for `caption` on admin UI Table component (Aman Pandey)
+ * Add API support for a redirects (contrib) endpoint (Rohit Sharma, Jaap Roes, Andreas Donig)
  * Fix: Update system check for overwriting storage backends to recognise the `STORAGES` setting introduced in Django 4.2 (phijma-leukeleu)
  * Fix: Prevent password change form from raising a validation error when browser autocomplete fills in the "Old password" field (Chiemezuo Akujobi)
  * Fix: Ensure that the legacy dropdown options, when closed, do not get accidentally clicked by other interactions wide viewports (CheesyPhoenix, Christer Jensen)

+ 1 - 0
CONTRIBUTORS.md

@@ -787,6 +787,7 @@
 * Jai Vignesh J
 * Sankalp
 * V Rohitansh
+* Andreas Donig
 
 ## Translators
 

+ 2 - 1
docs/advanced_topics/api/v2/configuration.md

@@ -40,11 +40,12 @@ content type (such as pages, images and documents) has its own endpoint.
 Endpoints are combined by a router, which provides the url configuration you
 can hook into the rest of your project.
 
-Wagtail provides three endpoint classes you can use:
+Wagtail provides multiple endpoint classes you can use:
 
 -   Pages {class}`wagtail.api.v2.views.PagesAPIViewSet`
 -   Images {class}`wagtail.images.api.v2.views.ImagesAPIViewSet`
 -   Documents {class}`wagtail.documents.api.v2.views.DocumentsAPIViewSet`
+-   Redirects {class}`wagtail.contrib.redirects.api.RedirectsAPIViewSet` see [](redirects_api_endpoint)
 
 You can subclass any of these endpoint classes to customise their functionality.
 For example, in this case if you need to change the `APIViewSet` by setting a desired renderer class:

+ 21 - 0
docs/reference/contrib/redirects.md

@@ -93,3 +93,24 @@ Options:
 
     .. automethod:: add_redirect
 ```
+
+(redirects_api_endpoint)=
+
+## API
+
+You can create an API endpoint to retrieve redirects or find specific redirects by path.
+
+See the [](api_v2_configuration) documentation on how to configure the Wagtail API.
+
+Add the following code to add the redirects endpoint:
+
+```python
+from wagtail.contrib.redirects.api import RedirectsAPIViewSet
+
+api_router.register_endpoint('redirects', RedirectsAPIViewSet)
+```
+
+With this configuration, redirects will be available at `/api/v2/redirects/`.
+
+Specific redirects by path can be resolved with `/api/v2/redirects/find/?html_path=<path>`,
+which will return either a `200` response with the redirects detail, or a `404` not found response.

+ 1 - 0
docs/releases/6.0.md

@@ -82,6 +82,7 @@ This feature was implemented by Nick Lee, Thibaud Colas, and Sage Abdullah.
  * Keep database state of pages and snippets updated while in draft state (Stefan Hammer)
  * Add `DrilldownController` and `w-drilldown` component to support drilldown menus (Thibaud Colas)
  * Add support for `caption` on admin UI Table component (Aman Pandey)
+ * Add API support for a [redirects (contrib)](redirects_api_endpoint) endpoint (Rohit Sharma, Jaap Roes, Andreas Donig)
 
 
 ### Bug fixes

+ 39 - 0
wagtail/contrib/redirects/api.py

@@ -0,0 +1,39 @@
+from django.http import Http404
+from rest_framework import serializers
+
+from wagtail.api.v2.filters import FieldsFilter, OrderingFilter, SearchFilter
+from wagtail.api.v2.serializers import BaseSerializer
+from wagtail.api.v2.views import BaseAPIViewSet
+from wagtail.contrib.redirects.middleware import get_redirect
+from wagtail.contrib.redirects.models import Redirect
+
+
+class RedirectSerializer(BaseSerializer):
+    location = serializers.CharField(source="link")
+
+
+class RedirectsAPIViewSet(BaseAPIViewSet):
+    base_serializer_class = RedirectSerializer
+    filter_backends = [FieldsFilter, OrderingFilter, SearchFilter]
+    body_fields = BaseAPIViewSet.body_fields + ["old_path", "location"]
+    name = "redirects"
+    model = Redirect
+
+    listing_default_fields = BaseAPIViewSet.listing_default_fields + [
+        "old_path",
+        "location",
+    ]
+
+    def find_object(self, queryset, request):
+        if "html_path" in request.GET:
+            redirect = get_redirect(
+                request,
+                request.GET["html_path"],
+            )
+
+            if redirect is None:
+                raise Http404
+            else:
+                return redirect
+
+        return super().find_object(queryset, request)

+ 105 - 0
wagtail/contrib/redirects/tests/test_redirects_api.py

@@ -0,0 +1,105 @@
+from django.test import TestCase
+from django.urls import reverse
+
+from wagtail.contrib.redirects.models import Redirect
+from wagtail.models import Page, Site
+
+
+class TestRedirectsAPI(TestCase):
+    def setUp(self):
+        self.example_home = Page.objects.get(slug="home").add_sibling(
+            instance=Page(title="Example Homepage", slug="example-home")
+        )
+        self.example_page = self.example_home.add_child(
+            instance=Page(title="Example Page", slug="example-page")
+        )
+        self.example_site = Site.objects.create(
+            hostname="example", root_page=self.example_home
+        )
+
+        Redirect.objects.create(
+            old_path="/hello-world",
+            site=self.example_site,
+            redirect_link="https://www.example.com/hello-world/",
+        )
+
+        Redirect.objects.create(
+            old_path="/good-work",
+            site=self.example_site,
+            redirect_link="https://www.example.com/hello-world/",
+        )
+
+        Redirect.add_redirect(
+            old_path="/hello-world", redirect_to="https://www.example.net/new-world/"
+        )
+
+        Redirect.add_redirect(
+            old_path="/old-example", redirect_to=self.example_home, is_permanent=False
+        )
+
+        Redirect.add_redirect(
+            old_path="/old-example?bar=foo&foo=bar",
+            redirect_to=self.example_page,
+            is_permanent=False,
+        )
+
+    def test_redirects_listing(self):
+        """Returns a list of all redirects"""
+
+        url = reverse("wagtailapi_v2:redirects:listing")
+
+        response = self.client.get(url)
+
+        self.assertEqual(response.status_code, 200)
+
+        self.assertEqual(5, len(response.json()["items"]))
+
+        item = response.json()["items"][0]
+
+        self.assertEqual("https://www.example.com/hello-world/", item["location"])
+        self.assertEqual("/hello-world", item["old_path"])
+
+    def test_redirect(self):
+        """Returns a matching (not site specific) redirect"""
+
+        url = reverse("wagtailapi_v2:redirects:find")
+
+        html_path = "/hello-world"
+
+        # Add the html_path to the URL
+        url += f"?html_path={html_path}"
+
+        response = self.client.get(url)
+
+        # Check for a redirect status code
+        self.assertEqual(response.status_code, 302)
+
+        # Follow the redirect to get the final response
+        response = self.client.get(response.url)
+        self.assertEqual(response.status_code, 200)
+        response_id = response.json()["id"]
+
+        expected_dict = {
+            "id": response_id,
+            "meta": {
+                "detail_url": f"http://localhost/api/main/redirects/{response_id}/",
+                "type": "wagtailredirects.Redirect",
+            },
+            "old_path": "/hello-world",
+            "location": "https://www.example.net/new-world/",
+        }
+
+        self.assertEqual(response.json(), expected_dict)
+
+    def test_html_path_without_redirect(self):
+        html_path = "/good-work"
+
+        url = reverse("wagtailapi_v2:redirects:find")
+
+        # Add the html_path to the URL
+        url += f"?html_path={html_path}"
+
+        response = self.client.get(url)
+
+        # Check for a 404 status code
+        self.assertEqual(response.status_code, 404)

+ 2 - 0
wagtail/test/urls.py

@@ -9,6 +9,7 @@ from wagtail.admin.views import home
 from wagtail.api.v2.router import WagtailAPIRouter
 from wagtail.api.v2.tests.test_pages import Test10411APIViewSet
 from wagtail.api.v2.views import PagesAPIViewSet
+from wagtail.contrib.redirects.api import RedirectsAPIViewSet
 from wagtail.contrib.sitemaps import Sitemap
 from wagtail.contrib.sitemaps import views as sitemaps_views
 from wagtail.documents import urls as wagtaildocs_urls
@@ -23,6 +24,7 @@ api_router = WagtailAPIRouter("wagtailapi_v2")
 api_router.register_endpoint("pages", PagesAPIViewSet)
 api_router.register_endpoint("images", ImagesAPIViewSet)
 api_router.register_endpoint("documents", DocumentsAPIViewSet)
+api_router.register_endpoint("redirects", RedirectsAPIViewSet)
 api_router.register_endpoint("issue_10411", Test10411APIViewSet)