Skip to content

Commit 8577444

Browse files
jrieffsbraun
andauthored
feat: merge page with node tree (#7947)
* WiP: Merge models Page and TreeNode * Merge models Page and TreeNode * Consistent warnings * Merge permission performance improvement * Minimal docs * Rename parent_node to parent_page in admin forms * Restore migration test * Fix migrations: auto field --------- Co-authored-by: Fabian Braun <fsbraun@gmx.de>
1 parent 8630db8 commit 8577444

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

46 files changed

+864
-732
lines changed

cms/admin/forms.py

Lines changed: 73 additions & 81 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
import warnings
2+
13
from django import forms
24
from django.apps import apps
35
from django.contrib.auth import get_permission_codename, get_user_model
@@ -35,7 +37,6 @@
3537
PageUser,
3638
PageUserGroup,
3739
Placeholder,
38-
TreeNode,
3940
)
4041
from cms.models.permissionmodels import User
4142
from cms.operations import ADD_PAGE_TRANSLATION, CHANGE_PAGE_TRANSLATION
@@ -46,6 +47,7 @@
4647
from cms.plugin_pool import plugin_pool
4748
from cms.signals.apphook import set_restart_trigger
4849
from cms.utils.compat.forms import UserChangeForm
50+
from cms.utils.compat.warnings import RemovedInDjangoCMS43Warning
4951
from cms.utils.conf import get_cms_setting
5052
from cms.utils.i18n import get_language_list, get_site_language_from_request
5153
from cms.utils.page import get_clean_username
@@ -246,8 +248,8 @@ class AddPageForm(BasePageContentForm):
246248
required=False,
247249
widget=forms.HiddenInput(),
248250
)
249-
parent_node = forms.ModelChoiceField(
250-
queryset=TreeNode.objects.all(),
251+
parent_page = forms.ModelChoiceField(
252+
queryset=Page.objects.all(),
251253
required=False,
252254
widget=forms.HiddenInput(),
253255
)
@@ -275,7 +277,7 @@ def __init__(self, *args, **kwargs):
275277
page_field = self.fields.get("cms_page")
276278

277279
if page_field:
278-
page_field.queryset = page_field.queryset.filter(node__site=self._site)
280+
page_field.queryset = page_field.queryset.filter(site=self._site)
279281

280282
root_page = PageType.get_root_page(site=self._site)
281283

@@ -302,11 +304,10 @@ def clean(self):
302304
# addressed first.
303305
return data
304306

305-
parent_node = data.get("parent_node")
306-
307-
if parent_node:
307+
parent_page = data.get("parent_page")
308+
if parent_page:
308309
slug = data["slug"]
309-
parent_path = parent_node.item.get_path(self._language)
310+
parent_path = parent_page.get_path(self._language)
310311
path = f"{parent_path}/{slug}" if parent_path else slug
311312
else:
312313
path = data["slug"]
@@ -325,12 +326,11 @@ def clean(self):
325326
data["path"] = path
326327
return data
327328

328-
def clean_parent_node(self):
329-
parent_node = self.cleaned_data.get("parent_node")
330-
331-
if parent_node and parent_node.site_id != self._site.pk:
329+
def clean_parent_page(self):
330+
parent_page = self.cleaned_data.get("parent_page")
331+
if parent_page and parent_page.site_id != self._site.pk:
332332
raise ValidationError("Site doesn't match the parent's page site")
333-
return parent_node
333+
return parent_page
334334

335335
def create_translation(self, page):
336336
data = self.cleaned_data
@@ -358,7 +358,7 @@ def create_translation(self, page):
358358
def from_source(self, source, parent=None):
359359
new_page = source.copy(
360360
site=self._site,
361-
parent_node=parent,
361+
parent_page=parent,
362362
language=self._language,
363363
translations=False,
364364
permissions=False,
@@ -377,7 +377,7 @@ def get_template(self):
377377
def save(self, *args, **kwargs):
378378
page = self.cleaned_data.get("cms_page")
379379
source = self.cleaned_data.get("source")
380-
parent = self.cleaned_data.get("parent_node")
380+
parent = self.cleaned_data.get("parent_page")
381381

382382
operation_token = send_pre_page_operation(
383383
request=self._request,
@@ -390,8 +390,8 @@ def save(self, *args, **kwargs):
390390
elif source:
391391
new_page = self.from_source(source, parent=parent)
392392
else:
393-
new_page = Page()
394-
new_page.set_tree_node(self._site, target=parent, position="last-child")
393+
new_page = Page(site=self._site, parent=parent)
394+
new_page.add_to_tree(position='last-child')
395395
new_page.save()
396396

397397
translation = self.create_translation(new_page)
@@ -415,8 +415,8 @@ def save(self, *args, **kwargs):
415415
)
416416

417417
is_first = not (
418-
TreeNode.objects.get_for_site(self._site)
419-
.exclude(pk=new_page.node_id)
418+
Page.objects.on_site(self._site)
419+
.exclude(pk=new_page.id)
420420
.exists()
421421
)
422422

@@ -456,7 +456,6 @@ def get_or_create_root(self):
456456
for the current site if it doesn't exist.
457457
"""
458458
root_page = PageType.get_root_page(site=self._site)
459-
460459
if not root_page:
461460
root_page = Page(is_page_type=True)
462461
root_page.set_tree_node(self._site)
@@ -471,24 +470,24 @@ def get_or_create_root(self):
471470
path=PAGE_TYPES_ID,
472471
in_navigation=False,
473472
)
474-
return root_page.node
473+
return root_page
475474

476-
def clean_parent_node(self):
477-
parent_node = super().clean_parent_node()
475+
def clean_parent_page(self):
476+
parent_page = super().clean_parent_page()
478477

479-
if parent_node and not parent_node.item.is_page_type:
478+
if parent_page and not parent_page.item.is_page_type:
480479
raise ValidationError("Parent has to be a page type.")
481480

482-
if not parent_node:
481+
if not parent_page:
483482
# parent was not explicitly selected.
484483
# fallback to the page types root
485-
parent_node = self.get_or_create_root()
486-
return parent_node
484+
parent_page = self.get_or_create_root()
485+
return parent_page
487486

488487
def from_source(self, source, parent=None):
489488
new_page = source.copy(
490489
site=self._site,
491-
parent_node=parent,
490+
parent_page=parent,
492491
language=self._language,
493492
translations=False,
494493
permissions=False,
@@ -604,15 +603,15 @@ def clean(self):
604603

605604
slug = data["slug"]
606605
path_override = self.cleaned_data.get("overwrite_url")
607-
parent_page = page.parent_page
608606

609607
if path_override:
610608
path = path_override.strip("/")
611-
elif parent_page and parent_page.is_home:
612-
path = slug
613-
elif parent_page:
614-
base_path = parent_page.get_path(self._language)
615-
path = f"{base_path}/{slug}" if base_path else None
609+
elif page.parent:
610+
if page.parent.is_home:
611+
path = slug
612+
else:
613+
base_path = page.parent.get_path(self._language)
614+
path = f'{base_path}/{slug}' if base_path else None
616615
else:
617616
path = slug
618617

@@ -926,49 +925,49 @@ def __init__(self, *args, **kwargs):
926925
self._site = kwargs.pop("site", Site.objects.get_current())
927926
super().__init__(*args, **kwargs)
928927
self.fields["target"].queryset = Page.objects.filter(
929-
node__site=self._site,
928+
site=self._site,
930929
is_page_type=self.page.is_page_type,
931930
)
932931

932+
def get_root_pages(self):
933+
pages = Page.get_root_nodes()
934+
return pages.exclude(is_page_type=not self.page.is_page_type)
935+
933936
def get_root_nodes(self):
934-
# TODO: this needs to avoid using the pages accessor directly
935-
nodes = TreeNode.get_root_nodes()
936-
return nodes.exclude(cms_pages__is_page_type=not self.page.is_page_type)
937+
warnings.warn(
938+
"Method `get_root_nodes()` is deprecated. Instead use method `get_root_pages`.",
939+
RemovedInDjangoCMS43Warning,
940+
stacklevel=2
941+
)
942+
return self.get_root_pages()
937943

938944
def get_tree_options(self):
939945
position = self.cleaned_data["position"]
940-
target_page = self.cleaned_data.get("target")
941-
parent_node = target_page.node if target_page else None
942-
943-
if parent_node:
944-
return self._get_tree_options_for_parent(parent_node, position)
946+
if target_page := self.cleaned_data.get("target"):
947+
return self._get_tree_options_for_parent(target_page, position)
945948
return self._get_tree_options_for_root(position)
946949

947950
def _get_tree_options_for_root(self, position):
948-
siblings = self.get_root_nodes().filter(site=self._site)
949-
951+
siblings = Page.get_root_nodes().filter(site=self._site)
950952
try:
951-
target_node = siblings[position]
953+
return siblings[position], 'left'
952954
except IndexError:
953955
# The position requested is not occupied.
954956
# Add the node as the last root node,
955957
# relative to the current site.
956-
return (siblings.reverse()[0], "right")
957-
return (target_node, "left")
958+
return siblings.reverse()[0], 'right'
958959

959-
def _get_tree_options_for_parent(self, parent_node, position):
960+
def _get_tree_options_for_parent(self, parent_page, position):
960961
if position == 0:
961-
return (parent_node, "first-child")
962-
963-
siblings = parent_node.get_children().filter(site=self._site)
962+
return parent_page, 'first-child'
964963

964+
siblings = parent_page.get_children().filter(site=self._site)
965965
try:
966-
target_node = siblings[position]
966+
return siblings[position], 'left'
967967
except IndexError:
968968
# The position requested is not occupied.
969969
# Add the node to be the parent's first child
970-
return (parent_node, "last-child")
971-
return (target_node, "left")
970+
return parent_page, 'last-child'
972971

973972

974973
class MovePageForm(PageTreeForm):
@@ -984,30 +983,23 @@ def clean(self):
984983
return cleaned_data
985984

986985
def get_tree_options(self):
987-
options = super().get_tree_options()
988-
target_node, target_node_position = options
989-
990-
if target_node_position != "left":
991-
return (target_node, target_node_position)
992-
993-
node = self.page.node
994-
node_is_first = node.path < target_node.path
995-
996-
if node_is_first and node.is_sibling_of(target_node):
997-
# The node being moved appears before the target node
998-
# and is a sibling of the target node.
999-
# The user is moving from left to right.
1000-
target_node_position = "right"
1001-
elif node_is_first:
1002-
# The node being moved appears before the target node
1003-
# but is not a sibling of the target node.
1004-
# The user is moving from right to left.
1005-
target_node_position = "left"
1006-
else:
1007-
# The node being moved appears after the target node.
986+
target_page, target_page_position = super().get_tree_options()
987+
if target_page_position != 'left':
988+
return target_page, target_page_position
989+
990+
if self.page.path < target_page.path:
991+
if self.page.is_sibling_of(target_page):
992+
# The page being moved appears before the target page and is a sibling of the target node.
993+
# The user is moving from left to right.
994+
return target_page, 'right'
995+
996+
# The node being moved appears before the target node but is not a sibling of the target node.
1008997
# The user is moving from right to left.
1009-
target_node_position = "left"
1010-
return (target_node, target_node_position)
998+
return target_page, 'left'
999+
1000+
# The node being moved appears after the target node.
1001+
# The user is moving from right to left.
1002+
return target_page, 'left'
10111003

10121004
def move_page(self):
10131005
self.page.move_page(*self.get_tree_options())
@@ -1018,10 +1010,10 @@ class CopyPageForm(PageTreeForm):
10181010
copy_permissions = forms.BooleanField(initial=False, required=False)
10191011

10201012
def copy_page(self, user):
1021-
target, position = self.get_tree_options()
1013+
target_page, position = self.get_tree_options()
10221014
copy_permissions = self.cleaned_data.get("copy_permissions", False)
10231015
new_page = self.page.copy_with_descendants(
1024-
target_node=target,
1016+
target_page=target_page,
10251017
position=position,
10261018
copy_permissions=copy_permissions,
10271019
target_site=self._site,
@@ -1035,7 +1027,7 @@ def _get_tree_options_for_root(self, position):
10351027
except IndexError:
10361028
# The user is copying a page to a site with no pages
10371029
# Add the node as the last root node.
1038-
siblings = self.get_root_nodes().reverse()
1030+
siblings = self.get_root_pages().reverse()
10391031
return (siblings[0], "right")
10401032

10411033

0 commit comments

Comments
 (0)