Skip to content

Commit 8d50660

Browse files
fix: Slug uniqueness not checked when moving page (#8185)
* fix: Slug uniqueness not checked when moving page * test: adds test for moving pages next to themselves * refactor: simplifies MovePageForm clean function * refactor: use UniqueConstraint instead of unique_together in pagemodel.py's PageUrl model * refactor: modifies BaseCMSTestCase to cleanup data between tests so that unique constraints are respected * Revert "refactor: modifies BaseCMSTestCase to cleanup data between tests so that unique constraints are respected" This reverts commit 3c5ac5d. * Revert "refactor: use UniqueConstraint instead of unique_together in pagemodel.py's PageUrl model" This reverts commit f1ead07. * refactor: use UniqueConstraint instead of unique_together in pagemodel.py's PageUrl model * refactor: allows identical paths + languages if they belong to different sites * refactor: supports pages in draft state and prioritizes published * Update test.yml * Use MySql 8.4 LTS * Update test.yml * refactor: removes unnecessary condition from pagemodel * cleanup: removes comment about supported python versions on test.yml * Remove the site field for PageUrl and add validator to `cms.api` * fix: Update migration (no need for deduplication) --------- Co-authored-by: Fabian Braun <fsbraun@gmx.de>
1 parent f26278c commit 8d50660

File tree

10 files changed

+1012
-723
lines changed

10 files changed

+1012
-723
lines changed

.github/workflows/test.yml

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ jobs:
1212
strategy:
1313
fail-fast: false
1414
matrix:
15-
python-version: [3.9, '3.10', '3.11', '3.12', '3.13.0-rc.2'] # latest release minus two
15+
python-version: [3.9, '3.10', '3.11', '3.12', '3.13']
1616
requirements-file: [
1717
django-4.2.txt,
1818
django-5.0.txt,
@@ -71,7 +71,7 @@ jobs:
7171
strategy:
7272
fail-fast: false
7373
matrix:
74-
python-version: [3.9, '3.10', '3.11', '3.12', '3.13.0-rc.2'] # latest release minus two
74+
python-version: [3.9, '3.10', '3.11', '3.12', '3.13']
7575
requirements-file: [
7676
django-4.2.txt,
7777
django-5.0.txt,
@@ -91,7 +91,7 @@ jobs:
9191

9292
services:
9393
mysql:
94-
image: mysql:9.0.1
94+
image: mysql:8.4
9595
env:
9696
MYSQL_ALLOW_EMPTY_PASSWORD: yes
9797
MYSQL_DATABASE: djangocms_test
@@ -129,7 +129,7 @@ jobs:
129129
strategy:
130130
fail-fast: false
131131
matrix:
132-
python-version: [3.9, '3.10', '3.11', '3.12', '3.13.0-rc.2'] # latest release minus two
132+
python-version: [3.9, '3.10', '3.11', '3.12', '3.13']
133133
requirements-file: [
134134
django-4.2.txt,
135135
django-5.0.txt,
@@ -262,7 +262,7 @@ jobs:
262262

263263
services:
264264
mysql:
265-
image: mysql:8.0
265+
image: mysql:8.4
266266
env:
267267
MYSQL_ALLOW_EMPTY_PASSWORD: yes
268268
MYSQL_DATABASE: djangocms_test

cms/admin/forms.py

Lines changed: 52 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -963,6 +963,25 @@ def clean(self):
963963
"target",
964964
force_str(_("You can't move the home page inside another page")),
965965
)
966+
return cleaned_data
967+
968+
target_page, position = self.get_tree_options()
969+
new_parent = self._determine_new_parent(target_page, position)
970+
971+
language = self.page.get_content_obj().language
972+
slug = self.page.get_slug(language)
973+
974+
if position not in ("first-child", "last-child"):
975+
self._validate_slug_uniqueness(new_parent, language, slug)
976+
977+
if new_parent:
978+
parent_path = new_parent.get_path(language)
979+
new_path = f"{parent_path}/{slug}" if parent_path else slug
980+
else:
981+
new_path = slug
982+
983+
self._validate_url_uniqueness(new_path, language)
984+
966985
return cleaned_data
967986

968987
def get_tree_options(self):
@@ -987,6 +1006,37 @@ def get_tree_options(self):
9871006
def move_page(self):
9881007
self.page.move_page(*self.get_tree_options())
9891008

1009+
def _determine_new_parent(self, target_page, position):
1010+
if position in ("first-child", "last-child"):
1011+
return target_page
1012+
return target_page.parent
1013+
1014+
def _validate_slug_uniqueness(self, parent, language, slug):
1015+
target_siblings = Page.objects.filter(parent=parent).exclude(pk=self.page.pk)
1016+
if target_siblings.filter(urls__slug=slug, urls__language=language).exists():
1017+
raise ValidationError(
1018+
_(
1019+
"You cannot have two pages with the same slug at the same page level. "
1020+
"Please alter the slug of one of the pages before trying to move again."
1021+
)
1022+
)
1023+
1024+
def _validate_url_uniqueness(self, path, language):
1025+
try:
1026+
validate_url_uniqueness(
1027+
self._site,
1028+
path=path,
1029+
language=language,
1030+
exclude_page=self.page,
1031+
)
1032+
except ValidationError:
1033+
raise ValidationError(
1034+
_(
1035+
"You cannot have two pages with the same slug at the same page level. "
1036+
"Please alter the slug of one of the pages before trying to move again."
1037+
)
1038+
)
1039+
9901040

9911041
class CopyPageForm(PageTreeForm):
9921042
source_site = forms.ModelChoiceField(queryset=Site.objects.all(), required=True)
@@ -1230,14 +1280,13 @@ def clean(self):
12301280

12311281
if data.get("can_delete_page"):
12321282
message = _(
1233-
"Users can't delete a page without permissions " "to change the page. Edit permissions required."
1283+
"Users can't delete a page without permissions to change the page. Edit permissions required."
12341284
)
12351285
raise ValidationError(message)
12361286

12371287
if data.get("can_add_pagepermission"):
12381288
message = _(
1239-
"Users can't set page permissions without permissions "
1240-
"to change a page. Edit permissions required."
1289+
"Users can't set page permissions without permissions to change a page. Edit permissions required."
12411290
)
12421291
raise ValidationError(message)
12431292

0 commit comments

Comments
 (0)