Skip to content

Commit 8c56f86

Browse files
committed
Handle url conflicts in move_page and copy_page.
Actually the latter doesn't need any special handling because pages are copied in unpublished state. In move_page checks are performed in check_title_slugs so that new slugs can be generated on the fly in case of conflicts. Related javascript admin functions has been updated to accept jsonified response
1 parent 213d282 commit 8c56f86

File tree

10 files changed

+147
-109
lines changed

10 files changed

+147
-109
lines changed

cms/admin/forms.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55
PageUserGroup)
66
from cms.utils.mail import mail_page_user_change
77
from cms.utils.page import is_valid_page_slug
8-
from cms.utils.page_resolver import get_page_from_path, is_valid_overwrite_url
8+
from cms.utils.page_resolver import get_page_from_path, is_valid_url
99
from cms.utils.permissions import (get_current_user, get_subordinate_users,
1010
get_subordinate_groups)
1111
from cms.utils.urlutils import any_path_re
@@ -126,7 +126,7 @@ def clean(self):
126126
title.slug = self.cleaned_data['slug']
127127
title.save()
128128
try:
129-
is_valid_overwrite_url(title.path,page)
129+
is_valid_url(title.path,page)
130130
except ValidationError,e:
131131
title.slug = oldslug
132132
title.save()
@@ -189,7 +189,7 @@ def clean(self):
189189
def clean_overwrite_url(self):
190190
if 'overwrite_url' in self.fields:
191191
url = self.cleaned_data['overwrite_url']
192-
is_valid_overwrite_url(url,self.instance)
192+
is_valid_url(url,self.instance)
193193
# TODO: Check what happens if 'overwrite_url' is NOT in self.fields
194194
return url
195195

cms/admin/pageadmin.py

Lines changed: 15 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@
1717
from cms.utils import (copy_plugins, helpers, moderator, permissions, plugins,
1818
get_template_from_request, get_language_from_request,
1919
placeholder as placeholder_utils, admin as admin_utils, cms_static_url)
20+
from cms.utils.page_resolver import is_valid_url
2021
from cms.utils.admin import jsonify_request
2122
from cms.utils.permissions import has_plugin_permission
2223
from copy import deepcopy
@@ -46,7 +47,6 @@
4647

4748
DJANGO_1_3 = LooseVersion(django.get_version()) < LooseVersion('1.4')
4849

49-
from cms.utils.page_resolver import is_valid_overwrite_url
5050

5151
if 'reversion' in settings.INSTALLED_APPS:
5252
import reversion
@@ -771,20 +771,20 @@ def move_page(self, request, page_id, extra_context=None):
771771
page = self.model.objects.get(pk=page_id)
772772
target = self.model.objects.get(pk=target)
773773
except self.model.DoesNotExist:
774-
return HttpResponseBadRequest("error")
774+
return jsonify_request(HttpResponseBadRequest("error"))
775775

776776
# does he haves permissions to do this...?
777777
if not page.has_move_page_permission(request) or \
778778
not target.has_add_permission(request):
779-
return HttpResponseForbidden("Denied")
779+
return jsonify_request(HttpResponseForbidden(_("Error! You don't have permissions to move this page. Please reload the page")))
780780

781781
# move page
782782
page.move_page(target, position)
783783

784784
if "reversion" in settings.INSTALLED_APPS:
785785
helpers.make_revision_with_plugins(page)
786-
787-
return admin_utils.render_admin_menu_item(request, page)
786+
787+
return jsonify_request(HttpResponse(admin_utils.render_admin_menu_item(request, page).content))
788788

789789
def get_permissions(self, request, page_id):
790790
page = get_object_or_404(Page, id=page_id)
@@ -839,12 +839,15 @@ def copy_page(self, request, page_id, extra_context=None):
839839
return HttpResponse("error")
840840
#context.update({'error': _('Page could not been moved.')})
841841
else:
842-
kwargs = {
843-
'copy_permissions': request.REQUEST.get('copy_permissions', False),
844-
'copy_moderation': request.REQUEST.get('copy_moderation', False),
845-
}
846-
page.copy_page(target, site, position, **kwargs)
847-
return HttpResponse("ok")
842+
try:
843+
kwargs = {
844+
'copy_permissions': request.REQUEST.get('copy_permissions', False),
845+
'copy_moderation': request.REQUEST.get('copy_moderation', False),
846+
}
847+
page.copy_page(target, site, position, **kwargs)
848+
return jsonify_request(HttpResponse("ok"))
849+
except ValidationError,e:
850+
return jsonify_request(HttpResponseBadRequest(e.messages))
848851
context.update(extra_context or {})
849852
return HttpResponseRedirect('../../')
850853

@@ -1061,7 +1064,7 @@ def change_status(self, request, page_id):
10611064
page = get_object_or_404(Page, pk=page_id)
10621065
if page.has_publish_permission(request):
10631066
try:
1064-
if page.published or is_valid_overwrite_url(page.get_absolute_url(),page,False):
1067+
if page.published or is_valid_url(page.get_absolute_url(),page,False):
10651068
page.published = not page.published
10661069
page.save()
10671070
return jsonify_request(HttpResponse(admin_utils.render_admin_menu_item(request, page).content))

cms/models/pagemodel.py

Lines changed: 11 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -124,11 +124,15 @@ def get_absolute_url(self, language=None, fallback=True):
124124
return urlutils.urljoin(reverse('pages-root'), path)
125125

126126
def move_page(self, target, position='first-child'):
127-
"""Called from admin interface when page is moved. Should be used on
127+
"""
128+
Called from admin interface when page is moved. Should be used on
128129
all the places which are changing page position. Used like an interface
129130
to mptt, but after move is done page_moved signal is fired.
131+
132+
Note for issue #1166: url conflicts are handled by updated
133+
check_title_slugs, overwrite_url on the moved page don't need any check
134+
as it remains the same regardless of the page position in the tree
130135
"""
131-
# TODO: Check for slug/overwrite_url clashes upon copying page (in the current page and all children pages)
132136
# make sure move_page does not break when using INHERIT template
133137
if (position in ('left', 'right')
134138
and not target.parent
@@ -155,8 +159,10 @@ def copy_page(self, target, site, position='first-child',
155159
156160
Note: public_copy was added in order to enable the creation of a copy for creating the public page during
157161
the publish operation as it sets the publisher_is_draft=False.
162+
163+
Note for issue #1166: when copying pages there is no need to check for conflicting
164+
URLs as pages as copied unplublished.
158165
"""
159-
# TODO: Check for slug/overwrite_url clashes upon copying page (in the current page and all children pages)
160166
from cms.utils.moderator import update_moderation_message
161167

162168
page_copy = None
@@ -270,6 +276,8 @@ def copy_page(self, target, site, position='first-child',
270276

271277
# create slug-copy for standard copy
272278
if not public_copy:
279+
title.save() # We need to save the title in order to
280+
# retrieve it in get_available_slug
273281
title.slug = page_utils.get_available_slug(title)
274282
title.save()
275283

cms/models/titlemodels.py

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,11 @@ def __unicode__(self):
3232
return "%s (%s)" % (self.title, self.slug)
3333

3434
def save(self, *args, **kwargs):
35+
# Update the path attribute before saving
36+
self.update_path()
37+
super(Title, self).save(*args, **kwargs)
38+
39+
def update_path(self):
3540
# Build path from parent page's path and slug
3641
current_path = self.path
3742
parent_page = self.page.parent
@@ -43,7 +48,6 @@ def save(self, *args, **kwargs):
4348
parent_title = Title.objects.get_title(parent_page, language=self.language, language_fallback=True)
4449
if parent_title:
4550
self.path = u'%s/%s' % (parent_title.path, slug)
46-
super(Title, self).save(*args, **kwargs)
4751

4852
@property
4953
def overwrite_url(self):

cms/static/cms/js/change_list.js

Lines changed: 67 additions & 62 deletions
Original file line numberDiff line numberDiff line change
@@ -223,37 +223,37 @@
223223
if(jtarget.hasClass("publish-checkbox")) {
224224
pageId = jtarget.attr("name").split("status-")[1];
225225
// if I don't put data in the post, django doesn't get it
226-
reloadItem(
227-
jtarget, admin_base_url + "cms/page/" + pageId + "/change-status/",
228-
{ 1:1 },
229-
// on success
230-
function(decoded,textStatus){
231-
response = decoded.content;
232-
status = decoded.status;
233-
if(status==200) {
234-
if (/page_\d+/.test($(jtarget).attr('id'))) {
235-
// one level higher
236-
target = $(jtarget).find('div.cont:first');
237-
} else {
238-
target = $(jtarget).parents('div.cont:first');
239-
}
226+
reloadItem(
227+
jtarget, admin_base_url + "cms/page/" + pageId + "/change-status/",
228+
{ 1:1 },
229+
// on success
230+
function(decoded,textStatus){
231+
response = decoded.content;
232+
status = decoded.status;
233+
if(status==200) {
234+
if (/page_\d+/.test($(jtarget).attr('id'))) {
235+
// one level higher
236+
target = $(jtarget).find('div.cont:first');
237+
} else {
238+
target = $(jtarget).parents('div.cont:first');
239+
}
240240

241-
var parent = target.parent();
242-
if (response == "NotFound") {
243-
return parent.remove();
244-
}
245-
target.replace(response);
246-
parent.find('div.cont:first').yft();
241+
var parent = target.parent();
242+
if (response == "NotFound") {
243+
return parent.remove();
244+
}
245+
target.replace(response);
246+
parent.find('div.cont:first').yft();
247247

248-
return true;
249-
}
250-
else {
251-
$(jtarget).attr("checked",false)
252-
alert(response);
253-
return false;
254-
}
255-
}
256-
);
248+
return false;
249+
}
250+
else {
251+
$(jtarget).attr("checked",false)
252+
alert(response);
253+
return false;
254+
}
255+
}
256+
);
257257
e.stopPropagation();
258258
return true;
259259
}
@@ -429,12 +429,15 @@
429429
};
430430
data = $.extend(data, options);
431431

432-
$.post("./" + item_id + "/copy-page/", data, function(html) {
433-
if(html=="ok"){
432+
$.post("./" + item_id + "/copy-page/", data, function(decoded) {
433+
response = decoded.content;
434+
status = decoded.status;
435+
if(status==200) {
434436
// reload tree
435437
window.location = window.location.href;
436438
}else{
437-
moveError($('#page_'+item_id + " div.col1:eq(0)"));
439+
alert(response);
440+
moveError($('#page_'+item_id + " div.col1:eq(0)"),response);
438441
}
439442
});
440443
}
@@ -468,24 +471,24 @@
468471
}
469472

470473
function onSuccess(response, textStatus) {
471-
status = true;
474+
status = true;
472475
if (callback) status = callback(response, textStatus);
473476

474-
if(status==true) {
475-
if (/page_\d+/.test($(el).attr('id'))) {
476-
// one level higher
477-
target = $(el).find('div.cont:first');
478-
} else {
479-
target = $(el).parents('div.cont:first');
480-
}
477+
if(status==true) {
478+
if (/page_\d+/.test($(el).attr('id'))) {
479+
// one level higher
480+
target = $(el).find('div.cont:first');
481+
} else {
482+
target = $(el).parents('div.cont:first');
483+
}
481484

482-
var parent = target.parent();
483-
if (response == "NotFound") {
484-
return parent.remove();
485-
}
486-
target.replace(response);
487-
parent.find('div.cont:first').yft();
488-
}
485+
var parent = target.parent();
486+
if (response == "NotFound") {
487+
return parent.remove();
488+
}
489+
target.replace(response);
490+
parent.find('div.cont:first').yft();
491+
}
489492

490493
return true;
491494
}
@@ -509,23 +512,25 @@
509512
reloadItem(
510513
jtarget, "./" + item_id + "/move-page/",
511514

512-
{ position: position, target: target_id },
513-
515+
{ position: position, target: target_id },
516+
514517
// on success
515-
function(){
516-
if (tree) {
517-
var tree_pos = {'left': 'before', 'right': 'after'}[position] || 'inside';
518-
tree.moved("#page_" + item_id, $("#page_" + target_id + " a.title")[0], tree_pos, false, false);
519-
} else {
520-
moveSuccess($('#page_'+item_id + " div.col1:eq(0)"));
518+
function(decoded,textStatus){
519+
response = decoded.content;
520+
status = decoded.status;
521+
if(status==200) {
522+
if (tree) {
523+
var tree_pos = {'left': 'before', 'right': 'after'}[position] || 'inside';
524+
tree.moved("#page_" + item_id, $("#page_" + target_id + " a.title")[0], tree_pos, false, false);
525+
} else {
526+
moveSuccess($('#page_'+item_id + " div.col1:eq(0)"));
527+
}
528+
return false;
529+
}
530+
else {
531+
moveError($('#page_'+item_id + " div.col1:eq(0)"),response);
532+
return false;
521533
}
522-
return true;
523-
},
524-
525-
// on error
526-
function(){
527-
moveError($('#page_'+item_id + " div.col1:eq(0)"));
528-
return false;
529534
}
530535
);
531536
}

cms/templates/admin/cms/page/change_list.html

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -60,8 +60,13 @@
6060
node.append(msg);
6161
msg.fadeOut(3000);
6262
}
63-
moveError = function(node){
64-
var msg = $({% javascript_string %}<span class="success">{% trans "An error occured. Please reload the page" %}</span>{% end_javascript_string %});
63+
moveError = function(node,message){
64+
if(message && message!="error") {
65+
var msg = $({% javascript_string %}<span class="success">{% end_javascript_string %}+message+{% javascript_string %}</span>{% end_javascript_string %});
66+
}
67+
else {
68+
var msg = $({% javascript_string %}<span class="success">{% trans "An error occured. Please reload the page" %}</span>{% end_javascript_string %});
69+
}
6570
node.append(msg);
6671
}
6772
// some settings used by javascript functions

cms/test_utils/testcases.py

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -166,8 +166,9 @@ def copy_page(self, page, target_page):
166166

167167
response = self.client.post(URL_CMS_PAGE + "%d/copy-page/" % page.pk, data)
168168
self.assertEquals(response.status_code, 200)
169-
self.assertEquals(response.content, "ok")
170-
169+
# Altered to reflect the new django-js jsonified response messages
170+
self.assertEquals(response.content, '{"status": 200, "content": "ok"}')
171+
171172
title = page.title_set.all()[0]
172173
copied_slug = get_available_slug(title)
173174

cms/tests/page.py

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@
1919
URL_CMS_PAGE_ADD)
2020
from cms.test_utils.util.context_managers import (LanguageOverride,
2121
SettingsOverride)
22-
from cms.utils.page_resolver import get_page_from_request, is_valid_overwrite_url
22+
from cms.utils.page_resolver import get_page_from_request, is_valid_url
2323
from django.conf import settings
2424
from django.contrib.sites.models import Site
2525
from django.core.exceptions import ValidationError
@@ -675,17 +675,17 @@ def test_slug_url_overwrite_clash(self):
675675
home = create_page('home', 'nav_playground.html', 'en', published=True)
676676
bar = create_page('bar', 'nav_playground.html', 'en', published=False)
677677
foo = create_page('foo', 'nav_playground.html', 'en', published=True)
678-
# Tests to assure is_valid_overwrite_url is ok on plain pages
679-
self.assertTrue(is_valid_overwrite_url(bar.get_absolute_url('en'),bar))
680-
self.assertTrue(is_valid_overwrite_url(foo.get_absolute_url('en'),foo))
678+
# Tests to assure is_valid_url is ok on plain pages
679+
self.assertTrue(is_valid_url(bar.get_absolute_url('en'),bar))
680+
self.assertTrue(is_valid_url(foo.get_absolute_url('en'),foo))
681681

682682
# Set url_overwrite for page foo
683683
title = foo.get_title_obj(language='en')
684684
title.has_url_overwrite = True
685685
title.path = '/bar/'
686686
title.save()
687687
try:
688-
url = is_valid_overwrite_url(bar.get_absolute_url('en'),bar)
688+
url = is_valid_url(bar.get_absolute_url('en'),bar)
689689
except ValidationError:
690690
url = False
691691
if url:

0 commit comments

Comments
 (0)