Add initial redirects (contrib) API endpoint

Builds on previous PRs #6110 & #8842
pull/11521/head
Rohit Sharma 2024-01-22 09:18:43 +00:00 zatwierdzone przez LB (Ben Johnston)
rodzic c4ef290859
commit 996abeae8e
8 zmienionych plików z 172 dodań i 1 usunięć

Wyświetl plik

@ -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)

Wyświetl plik

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

Wyświetl plik

@ -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:

Wyświetl plik

@ -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.

Wyświetl plik

@ -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

Wyświetl plik

@ -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)

Wyświetl plik

@ -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)

Wyświetl plik

@ -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)