We are currently solving the following issue within our repository. Here is the issue text:
--- BEGIN ISSUE ---
Issue with Cache in children endpoint
**This issue needs further investigation/testing**. For Malte the cache is currently disabled and the performance is acceptable. The priority of this issue depends on the performance of the migrated Integreat CMS.

### Describe the Bug
When calling random pages we sometimes get the following stack trace:
```
Feb 21 22:53:01 ERROR   django.request - Internal Server Error: /leakarlsruhefelsstrasse/de/wp-json/extensions/v3/children/
Traceback (most recent call last):
  File "/opt/integreat-cms/.venv/lib/python3.9/site-packages/django/core/handlers/exception.py", line 47, in inner
    response = get_response(request)
  File "/opt/integreat-cms/.venv/lib/python3.9/site-packages/django/core/handlers/base.py", line 181, in _get_response
    response = wrapped_callback(request, *callback_args, **callback_kwargs)
  File "/opt/integreat-cms/.venv/lib/python3.9/site-packages/integreat_cms/api/decorators.py", line 178, in wrap
    return func(request, *args, **kwargs)
  File "/opt/integreat-cms/.venv/lib/python3.9/site-packages/integreat_cms/api/decorators.py", line 124, in wrap
    return function(request, *args, **kwargs)
  File "/opt/integreat-cms/.venv/lib/python3.9/site-packages/integreat_cms/api/v3/pages.py", line 205, in children
    result.append(transform_page(public_translation))
  File "/opt/integreat-cms/.venv/lib/python3.9/site-packages/integreat_cms/api/v3/pages.py", line 22, in transform_page
    parent_page = page_translation.page.cached_parent
  File "/opt/integreat-cms/.venv/lib/python3.9/site-packages/django/utils/functional.py", line 48, in __get__
    res = instance.__dict__[self.name] = self.func(instance)
  File "/opt/integreat-cms/.venv/lib/python3.9/site-packages/integreat_cms/cms/models/abstract_tree_node.py", line 210, in cached_parent
    return self.get_cached_ancestors()[-1]
IndexError: list index out of range
```
This trace persists until the cache is flushed.

### Steps to Reproduce

Happens randomly. Visit pages until it happens.

For the time being we could use a simple filter instead of a cache operation in https://github.com/digitalfabrik/integreat-cms/blob/develop/integreat_cms/api/v3/pages.py#L231

--- END ISSUE ---

Below are some code segments, each from a relevant file. One or more of these files may contain bugs.
--- BEGIN FILES ---
Path: `integreat_cms/api/v3/pages.py`
Content:
```
1 """
2 This module includes functions related to the pages API endpoint.
3 """
4 from django.conf import settings
5 from django.http import JsonResponse, Http404
6 from django.shortcuts import get_object_or_404
7 
8 from ...cms.models import Page
9 from ..decorators import json_response, matomo_tracking
10 
11 
12 def transform_page(page_translation):
13     """
14     Function to create a dict from a single page_translation Object.
15 
16     :param page_translation: single page translation object
17     :type page_translation: ~integreat_cms.cms.models.pages.page_translation.PageTranslation
18 
19     :return: data necessary for API
20     :rtype: dict
21     """
22     parent_page = page_translation.page.cached_parent
23     if parent_page:
24         parent_absolute_url = parent_page.get_public_translation(
25             page_translation.language.slug
26         ).get_absolute_url()
27         parent = {
28             "id": parent_page.id,
29             "url": settings.BASE_URL + parent_absolute_url,
30             "path": parent_absolute_url,
31         }
32     else:
33         parent = {
34             "id": 0,
35             "url": None,
36             "path": None,
37         }
38     absolute_url = page_translation.get_absolute_url()
39     return {
40         "id": page_translation.id,
41         "url": settings.BASE_URL + absolute_url,
42         "path": absolute_url,
43         "title": page_translation.title,
44         "modified_gmt": page_translation.combined_last_updated,
45         "excerpt": page_translation.content,
46         "content": page_translation.combined_text,
47         "parent": parent,
48         "order": page_translation.page.lft,  # use left edge indicator of mptt model for order
49         "available_languages": page_translation.available_languages,
50         "thumbnail": page_translation.page.icon.url
51         if page_translation.page.icon
52         else None,
53         "hash": None,
54     }
55 
56 
57 @matomo_tracking
58 @json_response
59 # pylint: disable=unused-argument
60 def pages(request, region_slug, language_slug):
61     """
62     Function to iterate through all non-archived pages of a region and return them as JSON.
63 
64     :param request: Django request
65     :type request: ~django.http.HttpRequest
66     :param region_slug: slug of a region
67     :type region_slug: str
68     :param language_slug: language slug
69     :type language_slug: str
70 
71     :return: JSON object according to APIv3 pages endpoint definition
72     :rtype: ~django.http.JsonResponse
73     """
74     region = request.region
75     result = []
76     # The preliminary filter for explicitly_archived=False is not strictly required, but reduces the number of entries
77     # requested from the database
78     for page in region.pages.filter(explicitly_archived=False).cache_tree(
79         archived=False
80     )[0]:
81         page_translation = page.get_public_translation(language_slug)
82         if page_translation:
83             result.append(transform_page(page_translation))
84     return JsonResponse(
85         result, safe=False
86     )  # Turn off Safe-Mode to allow serializing arrays
87 
88 
89 def get_single_page(request, language_slug):
90     """
91     Helper function returning the desired page or a 404 if the
92     requested page does not exist.
93 
94     :param request: The request that has been sent to the Django server
95     :type request: ~django.http.HttpRequest
96 
97     :param language_slug: Code to identify the desired language
98     :type language_slug: str
99 
100     :raises ~django.http.Http404: HTTP status 404 if the request is malformed or no page with the given id or url exists.
101 
102     :raises RuntimeError: If neither the id nor the url parameter is given
103 
104     :return: the requested page
105     :rtype: ~integreat_cms.cms.models.pages.page.Page
106     """
107     region = request.region
108 
109     if request.GET.get("id"):
110         page = get_object_or_404(region.pages, id=request.GET.get("id"))
111 
112     elif request.GET.get("url"):
113         # Strip leading and trailing slashes to avoid ambiguous urls
114         url = request.GET.get("url").strip("/")
115         # The last path component of the url is the page translation slug
116         page_translation_slug = url.split("/")[-1]
117         # Get page by filtering for translation slug and translation language slug
118         filtered_pages = region.pages.filter(
119             translations__slug=page_translation_slug,
120             translations__language__slug=language_slug,
121         ).distinct()
122 
123         if len(filtered_pages) != 1:
124             raise Http404("No matching page translation found for url.")
125         page = filtered_pages[0]
126 
127     else:
128         raise RuntimeError("Either the id or the url parameter is required.")
129 
130     return page
131 
132 
133 @matomo_tracking
134 @json_response
135 # pylint: disable=unused-argument
136 def single_page(request, region_slug, language_slug):
137     """
138     View function returning the desired page as a JSON or a 404 if the
139     requested page does not exist.
140 
141     :param request: The request that has been sent to the Django server
142     :type request: ~django.http.HttpRequest
143 
144     :param region_slug: Slug defining the region
145     :type region_slug: str
146 
147     :param language_slug: Code to identify the desired language
148     :type language_slug: str
149 
150     :raises ~django.http.Http404: HTTP status 404 if the request is malformed or no page with the given id or url exists.
151 
152     :return: JSON with the requested page and a HTTP status 200.
153     :rtype: ~django.http.JsonResponse
154     """
155     try:
156         page = get_single_page(request, language_slug)
157     except RuntimeError as e:
158         return JsonResponse({"error": str(e)}, status=400)
159     # Get most recent public revision of the page
160     page_translation = page.get_public_translation(language_slug)
161     if page_translation:
162         return JsonResponse(transform_page(page_translation), safe=False)
163 
164     raise Http404("No Page matches the given url or id.")
165 
166 
167 @matomo_tracking
168 @json_response
169 # pylint: disable=unused-argument
170 def children(request, region_slug, language_slug):
171     """
172     Retrieves all children for a single page
173 
174     :param request: The request that has been sent to the Django server
175     :type request: ~django.http.HttpRequest
176 
177     :param region_slug: Slug defining the region
178     :type region_slug: str
179 
180     :param language_slug: Code to identify the desired language
181     :type language_slug: str
182 
183     :raises ~django.http.Http404: HTTP status 404 if the request is malformed or no page with the given id or url exists.
184 
185     :return: JSON with the requested page descendants
186     :rtype: ~django.http.JsonResponse
187     """
188     depth = int(request.GET.get("depth", 1))
189     try:
190         # try to get a single ancestor page based on the requests query string
191         root_pages = [get_single_page(request, language_slug)]
192     except RuntimeError:
193         # if neither id nor url is set then get all root pages
194         root_pages = Page.get_root_pages(region_slug)
195         # simulate a virtual root node for WP compatibility
196         # so that depth = 1 returns only those pages without parents (immediate children of this virtual root page)
197         # like in wordpress depth = 0 will return no results in this case
198         depth = depth - 1
199     result = []
200     for root in root_pages:
201         descendants = root.get_tree_max_depth(max_depth=depth)
202         for descendant in descendants:
203             public_translation = descendant.get_public_translation(language_slug)
204             if public_translation:
205                 result.append(transform_page(public_translation))
206     return JsonResponse(result, safe=False)
207 
208 
209 @json_response
210 # pylint: disable=unused-argument
211 def parents(request, region_slug, language_slug):
212     """
213     Retrieves all ancestors (parent and all nodes up to the root node) of a page
214 
215     :param request: The request that has been sent to the Django server
216     :type request: ~django.http.HttpRequest
217 
218     :param region_slug: Slug defining the region
219     :type region_slug: str
220 
221     :param language_slug: Code to identify the desired language
222     :type language_slug: str
223 
224     :raises ~django.http.Http404: HTTP status 404 if the request is malformed or no page with the given id or url exists.
225 
226     :return: JSON with the requested page ancestors
227     :rtype: ~django.http.JsonResponse
228     """
229     current_page = get_single_page(request, language_slug)
230     result = []
231     for ancestor in current_page.get_cached_ancestors(include_self=False):
232         public_translation = ancestor.get_public_translation(language_slug)
233         if not public_translation:
234             raise Http404("No Page matches the given url or id.")
235         result.append(transform_page(public_translation))
236     return JsonResponse(result, safe=False)
237
```
--- END FILES ---

Please first localize the bug based on the issue statement, and then generate a patch according to the `git diff` format fenced by three backticks.

Here is an example:

```diff
diff --git a/examples/server_async.py b/examples/server_async.py
--- a/examples/server_async.py
+++ b/examples/server_async.py
@@ -313,4 +313,4 @@
 
 
 if __name__ == "__main__":
-    asyncio.run(run_async_server("."), debug=True)
+    asyncio.run(run_async_server(), debug=True)
diff --git a/examples/server_sync.py b/examples/server_sync.py
--- a/examples/server_sync.py
+++ b/examples/server_sync.py
@@ -313,5 +313,5 @@
 
 
 if __name__ == "__main__":
-    server = run_sync_server(".")
+    server = run_sync_server()
     server.shutdown()

```

diff --git a/integreat_cms/api/v3/pages.py b/integreat_cms/api/v3/pages.py
--- a/integreat_cms/api/v3/pages.py
+++ b/integreat_cms/api/v3/pages.py
@@ -19,7 +19,7 @@
     :return: data necessary for API
     :rtype: dict
     """
-    parent_page = page_translation.page.cached_parent
+    parent_page = page_translation.page.parent
     if parent_page:
         parent_absolute_url = parent_page.get_public_translation(
             page_translation.language.slug
@@ -228,7 +228,7 @@
     """
     current_page = get_single_page(request, language_slug)
     result = []
-    for ancestor in current_page.get_cached_ancestors(include_self=False):
+    for ancestor in current_page.get_ancestors():
         public_translation = ancestor.get_public_translation(language_slug)
         if not public_translation:
             raise Http404("No Page matches the given url or id.")

{"golden_diff": "diff --git a/integreat_cms/api/v3/pages.py b/integreat_cms/api/v3/pages.py\n--- a/integreat_cms/api/v3/pages.py\n+++ b/integreat_cms/api/v3/pages.py\n@@ -19,7 +19,7 @@\n     :return: data necessary for API\n     :rtype: dict\n     \"\"\"\n-    parent_page = page_translation.page.cached_parent\n+    parent_page = page_translation.page.parent\n     if parent_page:\n         parent_absolute_url = parent_page.get_public_translation(\n             page_translation.language.slug\n@@ -228,7 +228,7 @@\n     \"\"\"\n     current_page = get_single_page(request, language_slug)\n     result = []\n-    for ancestor in current_page.get_cached_ancestors(include_self=False):\n+    for ancestor in current_page.get_ancestors():\n         public_translation = ancestor.get_public_translation(language_slug)\n         if not public_translation:\n             raise Http404(\"No Page matches the given url or id.\")\n", "issue": "Issue with Cache in children endpoint\n**This issue needs further investigation/testing**. For Malte the cache is currently disabled and the performance is acceptable. The priority of this issue depends on the performance of the migrated Integreat CMS.\r\n\r\n### Describe the Bug\r\nWhen calling random pages we sometimes get the following stack trace:\r\n```\r\nFeb 21 22:53:01 ERROR   django.request - Internal Server Error: /leakarlsruhefelsstrasse/de/wp-json/extensions/v3/children/\r\nTraceback (most recent call last):\r\n  File \"/opt/integreat-cms/.venv/lib/python3.9/site-packages/django/core/handlers/exception.py\", line 47, in inner\r\n    response = get_response(request)\r\n  File \"/opt/integreat-cms/.venv/lib/python3.9/site-packages/django/core/handlers/base.py\", line 181, in _get_response\r\n    response = wrapped_callback(request, *callback_args, **callback_kwargs)\r\n  File \"/opt/integreat-cms/.venv/lib/python3.9/site-packages/integreat_cms/api/decorators.py\", line 178, in wrap\r\n    return func(request, *args, **kwargs)\r\n  File \"/opt/integreat-cms/.venv/lib/python3.9/site-packages/integreat_cms/api/decorators.py\", line 124, in wrap\r\n    return function(request, *args, **kwargs)\r\n  File \"/opt/integreat-cms/.venv/lib/python3.9/site-packages/integreat_cms/api/v3/pages.py\", line 205, in children\r\n    result.append(transform_page(public_translation))\r\n  File \"/opt/integreat-cms/.venv/lib/python3.9/site-packages/integreat_cms/api/v3/pages.py\", line 22, in transform_page\r\n    parent_page = page_translation.page.cached_parent\r\n  File \"/opt/integreat-cms/.venv/lib/python3.9/site-packages/django/utils/functional.py\", line 48, in __get__\r\n    res = instance.__dict__[self.name] = self.func(instance)\r\n  File \"/opt/integreat-cms/.venv/lib/python3.9/site-packages/integreat_cms/cms/models/abstract_tree_node.py\", line 210, in cached_parent\r\n    return self.get_cached_ancestors()[-1]\r\nIndexError: list index out of range\r\n```\r\nThis trace persists until the cache is flushed.\r\n\r\n### Steps to Reproduce\r\n\r\nHappens randomly. Visit pages until it happens.\r\n\r\nFor the time being we could use a simple filter instead of a cache operation in https://github.com/digitalfabrik/integreat-cms/blob/develop/integreat_cms/api/v3/pages.py#L231\n", "before_files": [{"content": "\"\"\"\nThis module includes functions related to the pages API endpoint.\n\"\"\"\nfrom django.conf import settings\nfrom django.http import JsonResponse, Http404\nfrom django.shortcuts import get_object_or_404\n\nfrom ...cms.models import Page\nfrom ..decorators import json_response, matomo_tracking\n\n\ndef transform_page(page_translation):\n    \"\"\"\n    Function to create a dict from a single page_translation Object.\n\n    :param page_translation: single page translation object\n    :type page_translation: ~integreat_cms.cms.models.pages.page_translation.PageTranslation\n\n    :return: data necessary for API\n    :rtype: dict\n    \"\"\"\n    parent_page = page_translation.page.cached_parent\n    if parent_page:\n        parent_absolute_url = parent_page.get_public_translation(\n            page_translation.language.slug\n        ).get_absolute_url()\n        parent = {\n            \"id\": parent_page.id,\n            \"url\": settings.BASE_URL + parent_absolute_url,\n            \"path\": parent_absolute_url,\n        }\n    else:\n        parent = {\n            \"id\": 0,\n            \"url\": None,\n            \"path\": None,\n        }\n    absolute_url = page_translation.get_absolute_url()\n    return {\n        \"id\": page_translation.id,\n        \"url\": settings.BASE_URL + absolute_url,\n        \"path\": absolute_url,\n        \"title\": page_translation.title,\n        \"modified_gmt\": page_translation.combined_last_updated,\n        \"excerpt\": page_translation.content,\n        \"content\": page_translation.combined_text,\n        \"parent\": parent,\n        \"order\": page_translation.page.lft,  # use left edge indicator of mptt model for order\n        \"available_languages\": page_translation.available_languages,\n        \"thumbnail\": page_translation.page.icon.url\n        if page_translation.page.icon\n        else None,\n        \"hash\": None,\n    }\n\n\n@matomo_tracking\n@json_response\n# pylint: disable=unused-argument\ndef pages(request, region_slug, language_slug):\n    \"\"\"\n    Function to iterate through all non-archived pages of a region and return them as JSON.\n\n    :param request: Django request\n    :type request: ~django.http.HttpRequest\n    :param region_slug: slug of a region\n    :type region_slug: str\n    :param language_slug: language slug\n    :type language_slug: str\n\n    :return: JSON object according to APIv3 pages endpoint definition\n    :rtype: ~django.http.JsonResponse\n    \"\"\"\n    region = request.region\n    result = []\n    # The preliminary filter for explicitly_archived=False is not strictly required, but reduces the number of entries\n    # requested from the database\n    for page in region.pages.filter(explicitly_archived=False).cache_tree(\n        archived=False\n    )[0]:\n        page_translation = page.get_public_translation(language_slug)\n        if page_translation:\n            result.append(transform_page(page_translation))\n    return JsonResponse(\n        result, safe=False\n    )  # Turn off Safe-Mode to allow serializing arrays\n\n\ndef get_single_page(request, language_slug):\n    \"\"\"\n    Helper function returning the desired page or a 404 if the\n    requested page does not exist.\n\n    :param request: The request that has been sent to the Django server\n    :type request: ~django.http.HttpRequest\n\n    :param language_slug: Code to identify the desired language\n    :type language_slug: str\n\n    :raises ~django.http.Http404: HTTP status 404 if the request is malformed or no page with the given id or url exists.\n\n    :raises RuntimeError: If neither the id nor the url parameter is given\n\n    :return: the requested page\n    :rtype: ~integreat_cms.cms.models.pages.page.Page\n    \"\"\"\n    region = request.region\n\n    if request.GET.get(\"id\"):\n        page = get_object_or_404(region.pages, id=request.GET.get(\"id\"))\n\n    elif request.GET.get(\"url\"):\n        # Strip leading and trailing slashes to avoid ambiguous urls\n        url = request.GET.get(\"url\").strip(\"/\")\n        # The last path component of the url is the page translation slug\n        page_translation_slug = url.split(\"/\")[-1]\n        # Get page by filtering for translation slug and translation language slug\n        filtered_pages = region.pages.filter(\n            translations__slug=page_translation_slug,\n            translations__language__slug=language_slug,\n        ).distinct()\n\n        if len(filtered_pages) != 1:\n            raise Http404(\"No matching page translation found for url.\")\n        page = filtered_pages[0]\n\n    else:\n        raise RuntimeError(\"Either the id or the url parameter is required.\")\n\n    return page\n\n\n@matomo_tracking\n@json_response\n# pylint: disable=unused-argument\ndef single_page(request, region_slug, language_slug):\n    \"\"\"\n    View function returning the desired page as a JSON or a 404 if the\n    requested page does not exist.\n\n    :param request: The request that has been sent to the Django server\n    :type request: ~django.http.HttpRequest\n\n    :param region_slug: Slug defining the region\n    :type region_slug: str\n\n    :param language_slug: Code to identify the desired language\n    :type language_slug: str\n\n    :raises ~django.http.Http404: HTTP status 404 if the request is malformed or no page with the given id or url exists.\n\n    :return: JSON with the requested page and a HTTP status 200.\n    :rtype: ~django.http.JsonResponse\n    \"\"\"\n    try:\n        page = get_single_page(request, language_slug)\n    except RuntimeError as e:\n        return JsonResponse({\"error\": str(e)}, status=400)\n    # Get most recent public revision of the page\n    page_translation = page.get_public_translation(language_slug)\n    if page_translation:\n        return JsonResponse(transform_page(page_translation), safe=False)\n\n    raise Http404(\"No Page matches the given url or id.\")\n\n\n@matomo_tracking\n@json_response\n# pylint: disable=unused-argument\ndef children(request, region_slug, language_slug):\n    \"\"\"\n    Retrieves all children for a single page\n\n    :param request: The request that has been sent to the Django server\n    :type request: ~django.http.HttpRequest\n\n    :param region_slug: Slug defining the region\n    :type region_slug: str\n\n    :param language_slug: Code to identify the desired language\n    :type language_slug: str\n\n    :raises ~django.http.Http404: HTTP status 404 if the request is malformed or no page with the given id or url exists.\n\n    :return: JSON with the requested page descendants\n    :rtype: ~django.http.JsonResponse\n    \"\"\"\n    depth = int(request.GET.get(\"depth\", 1))\n    try:\n        # try to get a single ancestor page based on the requests query string\n        root_pages = [get_single_page(request, language_slug)]\n    except RuntimeError:\n        # if neither id nor url is set then get all root pages\n        root_pages = Page.get_root_pages(region_slug)\n        # simulate a virtual root node for WP compatibility\n        # so that depth = 1 returns only those pages without parents (immediate children of this virtual root page)\n        # like in wordpress depth = 0 will return no results in this case\n        depth = depth - 1\n    result = []\n    for root in root_pages:\n        descendants = root.get_tree_max_depth(max_depth=depth)\n        for descendant in descendants:\n            public_translation = descendant.get_public_translation(language_slug)\n            if public_translation:\n                result.append(transform_page(public_translation))\n    return JsonResponse(result, safe=False)\n\n\n@json_response\n# pylint: disable=unused-argument\ndef parents(request, region_slug, language_slug):\n    \"\"\"\n    Retrieves all ancestors (parent and all nodes up to the root node) of a page\n\n    :param request: The request that has been sent to the Django server\n    :type request: ~django.http.HttpRequest\n\n    :param region_slug: Slug defining the region\n    :type region_slug: str\n\n    :param language_slug: Code to identify the desired language\n    :type language_slug: str\n\n    :raises ~django.http.Http404: HTTP status 404 if the request is malformed or no page with the given id or url exists.\n\n    :return: JSON with the requested page ancestors\n    :rtype: ~django.http.JsonResponse\n    \"\"\"\n    current_page = get_single_page(request, language_slug)\n    result = []\n    for ancestor in current_page.get_cached_ancestors(include_self=False):\n        public_translation = ancestor.get_public_translation(language_slug)\n        if not public_translation:\n            raise Http404(\"No Page matches the given url or id.\")\n        result.append(transform_page(public_translation))\n    return JsonResponse(result, safe=False)\n", "path": "integreat_cms/api/v3/pages.py"}], "after_files": [{"content": "\"\"\"\nThis module includes functions related to the pages API endpoint.\n\"\"\"\nfrom django.conf import settings\nfrom django.http import JsonResponse, Http404\nfrom django.shortcuts import get_object_or_404\n\nfrom ...cms.models import Page\nfrom ..decorators import json_response, matomo_tracking\n\n\ndef transform_page(page_translation):\n    \"\"\"\n    Function to create a dict from a single page_translation Object.\n\n    :param page_translation: single page translation object\n    :type page_translation: ~integreat_cms.cms.models.pages.page_translation.PageTranslation\n\n    :return: data necessary for API\n    :rtype: dict\n    \"\"\"\n    parent_page = page_translation.page.parent\n    if parent_page:\n        parent_absolute_url = parent_page.get_public_translation(\n            page_translation.language.slug\n        ).get_absolute_url()\n        parent = {\n            \"id\": parent_page.id,\n            \"url\": settings.BASE_URL + parent_absolute_url,\n            \"path\": parent_absolute_url,\n        }\n    else:\n        parent = {\n            \"id\": 0,\n            \"url\": None,\n            \"path\": None,\n        }\n    absolute_url = page_translation.get_absolute_url()\n    return {\n        \"id\": page_translation.id,\n        \"url\": settings.BASE_URL + absolute_url,\n        \"path\": absolute_url,\n        \"title\": page_translation.title,\n        \"modified_gmt\": page_translation.combined_last_updated,\n        \"excerpt\": page_translation.content,\n        \"content\": page_translation.combined_text,\n        \"parent\": parent,\n        \"order\": page_translation.page.lft,  # use left edge indicator of mptt model for order\n        \"available_languages\": page_translation.available_languages,\n        \"thumbnail\": page_translation.page.icon.url\n        if page_translation.page.icon\n        else None,\n        \"hash\": None,\n    }\n\n\n@matomo_tracking\n@json_response\n# pylint: disable=unused-argument\ndef pages(request, region_slug, language_slug):\n    \"\"\"\n    Function to iterate through all non-archived pages of a region and return them as JSON.\n\n    :param request: Django request\n    :type request: ~django.http.HttpRequest\n    :param region_slug: slug of a region\n    :type region_slug: str\n    :param language_slug: language slug\n    :type language_slug: str\n\n    :return: JSON object according to APIv3 pages endpoint definition\n    :rtype: ~django.http.JsonResponse\n    \"\"\"\n    region = request.region\n    result = []\n    # The preliminary filter for explicitly_archived=False is not strictly required, but reduces the number of entries\n    # requested from the database\n    for page in region.pages.filter(explicitly_archived=False).cache_tree(\n        archived=False\n    )[0]:\n        page_translation = page.get_public_translation(language_slug)\n        if page_translation:\n            result.append(transform_page(page_translation))\n    return JsonResponse(\n        result, safe=False\n    )  # Turn off Safe-Mode to allow serializing arrays\n\n\ndef get_single_page(request, language_slug):\n    \"\"\"\n    Helper function returning the desired page or a 404 if the\n    requested page does not exist.\n\n    :param request: The request that has been sent to the Django server\n    :type request: ~django.http.HttpRequest\n\n    :param language_slug: Code to identify the desired language\n    :type language_slug: str\n\n    :raises ~django.http.Http404: HTTP status 404 if the request is malformed or no page with the given id or url exists.\n\n    :raises RuntimeError: If neither the id nor the url parameter is given\n\n    :return: the requested page\n    :rtype: ~integreat_cms.cms.models.pages.page.Page\n    \"\"\"\n    region = request.region\n\n    if request.GET.get(\"id\"):\n        page = get_object_or_404(region.pages, id=request.GET.get(\"id\"))\n\n    elif request.GET.get(\"url\"):\n        # Strip leading and trailing slashes to avoid ambiguous urls\n        url = request.GET.get(\"url\").strip(\"/\")\n        # The last path component of the url is the page translation slug\n        page_translation_slug = url.split(\"/\")[-1]\n        # Get page by filtering for translation slug and translation language slug\n        filtered_pages = region.pages.filter(\n            translations__slug=page_translation_slug,\n            translations__language__slug=language_slug,\n        ).distinct()\n\n        if len(filtered_pages) != 1:\n            raise Http404(\"No matching page translation found for url.\")\n        page = filtered_pages[0]\n\n    else:\n        raise RuntimeError(\"Either the id or the url parameter is required.\")\n\n    return page\n\n\n@matomo_tracking\n@json_response\n# pylint: disable=unused-argument\ndef single_page(request, region_slug, language_slug):\n    \"\"\"\n    View function returning the desired page as a JSON or a 404 if the\n    requested page does not exist.\n\n    :param request: The request that has been sent to the Django server\n    :type request: ~django.http.HttpRequest\n\n    :param region_slug: Slug defining the region\n    :type region_slug: str\n\n    :param language_slug: Code to identify the desired language\n    :type language_slug: str\n\n    :raises ~django.http.Http404: HTTP status 404 if the request is malformed or no page with the given id or url exists.\n\n    :return: JSON with the requested page and a HTTP status 200.\n    :rtype: ~django.http.JsonResponse\n    \"\"\"\n    try:\n        page = get_single_page(request, language_slug)\n    except RuntimeError as e:\n        return JsonResponse({\"error\": str(e)}, status=400)\n    # Get most recent public revision of the page\n    page_translation = page.get_public_translation(language_slug)\n    if page_translation:\n        return JsonResponse(transform_page(page_translation), safe=False)\n\n    raise Http404(\"No Page matches the given url or id.\")\n\n\n@matomo_tracking\n@json_response\n# pylint: disable=unused-argument\ndef children(request, region_slug, language_slug):\n    \"\"\"\n    Retrieves all children for a single page\n\n    :param request: The request that has been sent to the Django server\n    :type request: ~django.http.HttpRequest\n\n    :param region_slug: Slug defining the region\n    :type region_slug: str\n\n    :param language_slug: Code to identify the desired language\n    :type language_slug: str\n\n    :raises ~django.http.Http404: HTTP status 404 if the request is malformed or no page with the given id or url exists.\n\n    :return: JSON with the requested page descendants\n    :rtype: ~django.http.JsonResponse\n    \"\"\"\n    depth = int(request.GET.get(\"depth\", 1))\n    try:\n        # try to get a single ancestor page based on the requests query string\n        root_pages = [get_single_page(request, language_slug)]\n    except RuntimeError:\n        # if neither id nor url is set then get all root pages\n        root_pages = Page.get_root_pages(region_slug)\n        # simulate a virtual root node for WP compatibility\n        # so that depth = 1 returns only those pages without parents (immediate children of this virtual root page)\n        # like in wordpress depth = 0 will return no results in this case\n        depth = depth - 1\n    result = []\n    for root in root_pages:\n        descendants = root.get_tree_max_depth(max_depth=depth)\n        for descendant in descendants:\n            public_translation = descendant.get_public_translation(language_slug)\n            if public_translation:\n                result.append(transform_page(public_translation))\n    return JsonResponse(result, safe=False)\n\n\n@json_response\n# pylint: disable=unused-argument\ndef parents(request, region_slug, language_slug):\n    \"\"\"\n    Retrieves all ancestors (parent and all nodes up to the root node) of a page\n\n    :param request: The request that has been sent to the Django server\n    :type request: ~django.http.HttpRequest\n\n    :param region_slug: Slug defining the region\n    :type region_slug: str\n\n    :param language_slug: Code to identify the desired language\n    :type language_slug: str\n\n    :raises ~django.http.Http404: HTTP status 404 if the request is malformed or no page with the given id or url exists.\n\n    :return: JSON with the requested page ancestors\n    :rtype: ~django.http.JsonResponse\n    \"\"\"\n    current_page = get_single_page(request, language_slug)\n    result = []\n    for ancestor in current_page.get_ancestors():\n        public_translation = ancestor.get_public_translation(language_slug)\n        if not public_translation:\n            raise Http404(\"No Page matches the given url or id.\")\n        result.append(transform_page(public_translation))\n    return JsonResponse(result, safe=False)\n", "path": "integreat_cms/api/v3/pages.py"}]}