Repository: digitalfabrik/integreat-cms

Issue:
Prefetch subpages in advance
### Motivation
<!-- A clear and concise description of what the motivation for the new feature is, and what problem it is solving. -->
In PR #1149, we introduced the dynamic fetching of subpages in the page tree. Everytime the child pages of a parent page are expanded, an ajax request is performed to retrieve the result from the server, which causes the expansion to take a small loading time:

![Peek 2022-02-10 13-11](https://user-images.githubusercontent.com/16021269/153406471-9dbdf081-ce78-4b49-abab-9cce3240e6b2.gif)

### Proposed Solution
<!-- A clear and concise description of the feature you would like to add, and how it solves the motivating problem. -->
It would be cool if this delay could be reduced, e.g. by automatically loading the subpages before the expansion button is clicked (but after the rest of the root pages are rendered completely, it shouldn't slow down the loading time of the initial page tree). We could recursively walk the tree completely or only load n+1 levels depending on which pages are currently expanded.
The subpages should then expand instantly after the button is clicked:

![Peek 2022-02-10 13-11-2](https://user-images.githubusercontent.com/16021269/153406503-60415b62-60c6-4021-a3fb-c27cb265d7f4.gif)


### Alternatives
<!-- A clear and concise description of any alternative solutions or features you've considered, and why you're proposed solution is better. -->


### Additional Context
<!-- Add any other information or screenshots about the feature request here. -->
There are two tickets that depend on this issue:
- #1175
- #1244



Discussion:
When implementing this, #1175 can probably implemented, too.
Right now this is only partially implemented: The empty label only gets shown if a page has no subpages at all, but it should also be shown if e.g. all its subpages are archived.

Resolving patch:
diff --git a/integreat_cms/cms/urls/protected.py b/integreat_cms/cms/urls/protected.py
--- a/integreat_cms/cms/urls/protected.py
+++ b/integreat_cms/cms/urls/protected.py
@@ -468,10 +468,9 @@
                                             ),
                                         ),
                                         path(
-                                            "partial-page-tree/<slug:language_slug>/tree-<int:tree_id>/"
-                                            "lft-<int:lft>/rgt-<int:rgt>/depth-<int:depth>/",
+                                            "partial-page-tree/<slug:language_slug>/tree-<int:tree_id>/",
                                             pages.PartialPageTreeView.as_view(),
-                                            name="get_page_children_ajax",
+                                            name="get_page_tree_ajax",
                                         ),
                                     ]
                                 ),
diff --git a/integreat_cms/cms/views/pages/page_tree_view.py b/integreat_cms/cms/views/pages/page_tree_view.py
--- a/integreat_cms/cms/views/pages/page_tree_view.py
+++ b/integreat_cms/cms/views/pages/page_tree_view.py
@@ -101,6 +101,7 @@ def get(self, request, *args, **kwargs):
         )
 
         if filter_data:
+            logger.debug("Page tree filtered with data %r", filter_data)
             # Set data for filter form rendering
             filter_form = PageFilterForm(data=filter_data)
             pages = self.filter_pages(pages, language_slug, filter_form)
@@ -183,4 +184,6 @@ def filter_pages(pages, language_slug, filter_form):
                     if translation_state and translation_state[1] in selected_status:
                         filtered_pages.append(page)
                 pages = filtered_pages
+        else:
+            filter_form.changed_data.clear()
         return pages
diff --git a/integreat_cms/cms/views/pages/partial_page_tree_view.py b/integreat_cms/cms/views/pages/partial_page_tree_view.py
--- a/integreat_cms/cms/views/pages/partial_page_tree_view.py
+++ b/integreat_cms/cms/views/pages/partial_page_tree_view.py
@@ -1,4 +1,3 @@
-from django.db.models import Q
 from django.shortcuts import render
 from django.utils.decorators import method_decorator
 from django.views.generic import TemplateView
@@ -20,7 +19,7 @@ class PartialPageTreeView(TemplateView, PageContextMixin):
     # pylint: disable=unused-argument
     def get(self, request, *args, **kwargs):
         r"""
-        Retrieve the rendered subtree of a given parent page
+        Retrieve the rendered subtree of a given root page
 
         :param request: The current request
         :type request: ~django.http.HttpResponse
@@ -35,37 +34,26 @@ def get(self, request, *args, **kwargs):
         :rtype: ~django.template.response.TemplateResponse
         """
         tree_id = int(kwargs.get("tree_id"))
-        lft = int(kwargs.get("lft"))
-        rgt = int(kwargs.get("rgt"))
-        depth = int(kwargs.get("depth"))
         region = request.region
         language = region.get_language_or_404(
             kwargs.get("language_slug"), only_active=True
         )
-        # List of all ancestors, the requested parent and all direct children
+        # Get the tree of the given id
         pages = (
-            region.pages.filter(
-                # This condition queries the parent and all direct children
-                Q(tree_id=tree_id, lft__range=(lft, rgt - 1), depth__lte=depth + 1)
-                # This condition queries all ancestors
-                | Q(tree_id=tree_id, lft__lt=lft, rgt__gt=rgt)
-            )
+            region.pages.filter(tree_id=tree_id)
             .prefetch_major_public_translations()
             .cache_tree(False)
         )
-        # If the depth is 1, the first element must be the direct parent, etc.
-        parent = pages[depth - 1]
-        # The remaining pages are the direct children
-        children = pages[depth:]
-        # For every depth level, exactly one ancestor is in the list
-        ancestor_ids = [ancestor.id for ancestor in pages[:depth]]
+        # The first element must be the root node
+        parent = pages[0]
+        # The remaining pages are the descendants
+        children = pages[1:]
         return render(
             request,
             self.template,
             {
                 **self.get_context_data(**kwargs),
                 "pages": children,
-                "ancestor_ids": ancestor_ids,
                 "language": language,
                 "languages": region.active_languages,
                 "parent_id": parent.id,


Test patch:
diff --git a/tests/cms/views/view_config.py b/tests/cms/views/view_config.py
--- a/tests/cms/views/view_config.py
+++ b/tests/cms/views/view_config.py
@@ -364,27 +364,21 @@
         {"region_slug": "nurnberg", "page_id": 8},
     ),
     (
-        [("get_page_children_ajax", STAFF_ROLES + [MANAGEMENT, EDITOR])],
+        [("get_page_tree_ajax", STAFF_ROLES + [MANAGEMENT, EDITOR])],
         # The kwargs for these views
         {
             "region_slug": "augsburg",
             "language_slug": "de",
             "tree_id": 2,
-            "lft": 1,
-            "rgt": 12,
-            "depth": 1,
         },
     ),
     (
-        [("get_page_children_ajax", STAFF_ROLES)],
+        [("get_page_tree_ajax", STAFF_ROLES)],
         # The kwargs for these views
         {
             "region_slug": "nurnberg",
             "language_slug": "de",
             "tree_id": 1,
-            "lft": 1,
-            "rgt": 14,
-            "depth": 1,
         },
     ),
     (