Repository: wagtail/wagtail

Issue:
Too long url_path causes crash
The slug isn't being validated properly at the form level. When users add a too long slug, everything crashes as this slug gets sent to the database without being checked first.

PR will follow shortly...



Discussion:
The problem is not with the validation on the slug field - the input field sets a max length of 255, and if a longer string is passed in (by hacking the input field HTML) the validation kicks in as expected.

The problem is that `url_path` also has a limit of 255 characters, so a slug that is close to this length is liable to push it over the limit. This is only really an issue since we increased the max slug length from 50 to 255 in bd28d9288ab1d718e2e3a3a78d5e9a263c244fa7 - it's possible to run into this on 0.8.x too, but only by going >5 levels deep with long slugs at each level. Given that the fix is going to involve a schema change, I propose that we fix this for 1.0 instead.


Resolving patch:
diff --git a/wagtail/wagtailcore/migrations/0016_change_page_url_path_to_text_field.py b/wagtail/wagtailcore/migrations/0016_change_page_url_path_to_text_field.py
new file mode 100644
--- /dev/null
+++ b/wagtail/wagtailcore/migrations/0016_change_page_url_path_to_text_field.py
@@ -0,0 +1,20 @@
+# -*- coding: utf-8 -*-
+from __future__ import unicode_literals
+
+from django.db import models, migrations
+
+
+class Migration(migrations.Migration):
+
+    dependencies = [
+        ('wagtailcore', '0015_add_more_verbose_names'),
+    ]
+
+    operations = [
+        migrations.AlterField(
+            model_name='page',
+            name='url_path',
+            field=models.TextField(verbose_name='URL path', editable=False, blank=True),
+            preserve_default=True,
+        ),
+    ]
diff --git a/wagtail/wagtailcore/models.py b/wagtail/wagtailcore/models.py
--- a/wagtail/wagtailcore/models.py
+++ b/wagtail/wagtailcore/models.py
@@ -274,7 +274,7 @@ class Page(six.with_metaclass(PageBase, MP_Node, ClusterableModel, index.Indexed
     content_type = models.ForeignKey('contenttypes.ContentType', verbose_name=_('Content type'), related_name='pages')
     live = models.BooleanField(verbose_name=_('Live'), default=True, editable=False)
     has_unpublished_changes = models.BooleanField(verbose_name=_('Has unpublished changes'), default=False, editable=False)
-    url_path = models.CharField(verbose_name=_('URL path'), max_length=255, blank=True, editable=False)
+    url_path = models.TextField(verbose_name=_('URL path'), blank=True, editable=False)
     owner = models.ForeignKey(settings.AUTH_USER_MODEL, verbose_name=_('Owner'), null=True, blank=True, editable=False, on_delete=models.SET_NULL, related_name='owned_pages')
 
     seo_title = models.CharField(verbose_name=_("Page title"), max_length=255, blank=True, help_text=_("Optional. 'Search Engine Friendly' title. This will appear at the top of the browser window."))


Test patch:
diff --git a/wagtail/wagtailcore/tests/test_page_model.py b/wagtail/wagtailcore/tests/test_page_model.py
--- a/wagtail/wagtailcore/tests/test_page_model.py
+++ b/wagtail/wagtailcore/tests/test_page_model.py
@@ -677,3 +677,30 @@ def test_publish_revision_doesnt_remove_latest_revision_created_at(self):
 
         # Check that latest_revision_created_at is still set
         self.assertIsNotNone(Page.objects.get(id=1).latest_revision_created_at)
+
+
+class TestIssue1216(TestCase):
+    """
+    Test that url paths greater than 255 characters are supported
+    """
+    fixtures = ['test.json']
+
+    def test_url_path_can_exceed_255_characters(self):
+        event_index = Page.objects.get(url_path='/home/events/')
+        christmas_event = EventPage.objects.get(url_path='/home/events/christmas/')
+
+        # Change the christmas_event slug first - this way, we test that the process for
+        # updating child url paths also handles >255 character paths correctly
+        new_christmas_slug = "christmas-%s-christmas" % ("0123456789" * 20)
+        christmas_event.slug = new_christmas_slug
+        christmas_event.save_revision().publish()
+
+        # Change the event index slug and publish it
+        new_event_index_slug = "events-%s-events" % ("0123456789" * 20)
+        event_index.slug = new_event_index_slug
+        event_index.save_revision().publish()
+
+        # Check that the url path updated correctly
+        new_christmas_event = EventPage.objects.get(id=christmas_event.id)
+        expected_url_path = "/home/%s/%s/" % (new_event_index_slug, new_christmas_slug)
+        self.assertEqual(new_christmas_event.url_path, expected_url_path)