Просмотр исходного кода

Fix URL character validation in dashboard PageUpdateForm.

Also fix the max_length on the url field. The underlying model field has
a max_length of 100, so it is invalid to have a larger value here.

DRY out dashboard page update/create views.

Refactor tests - perform tests of form logic directly rather than indirectly through view code.

Fixes #2559.
master
Samir Shah 8 лет назад
Родитель
Сommit
89ff0b3da4

+ 3
- 0
docs/source/releases/v1.6.rst Просмотреть файл

@@ -47,6 +47,9 @@ Minor changes
47 47
    initiation.
48 48
  - Added ``get_stock_info`` hook to ``oscar.apps.basket.models.Basket``  for
49 49
    implementing strategies that depend on product options.
50
+ - Fixed the page create/update views in the dashboard to correctly validate
51
+   URLs. Invalid characters in URLs will raise a validation error, as will
52
+   URLs longer than 100 characters.
50 53
 
51 54
 .. _incompatible_in_1.6:
52 55
 

+ 13
- 2
src/oscar/apps/dashboard/pages/forms.py Просмотреть файл

@@ -22,8 +22,19 @@ class PageUpdateForm(forms.ModelForm):
22 22
     and *content* field. The specified URL will be validated and check if
23 23
     the same URL already exists in the system.
24 24
     """
25
-    url = forms.CharField(max_length=128, required=False, label=_("URL"),
26
-                          help_text=_("Example: '/about/contact/'."))
25
+    url = forms.RegexField(
26
+        label=_("URL"),
27
+        max_length=100,
28
+        regex=r'^[-\w/\.~]+$',
29
+        required=False,
30
+        help_text=_("Example: '/about/contact/'."),
31
+        error_messages={
32
+            "invalid": _(
33
+                "This value must contain only letters, numbers, dots, "
34
+                "underscores, dashes, slashes or tildes."
35
+            ),
36
+        },
37
+    )
27 38
 
28 39
     def clean_url(self):
29 40
         """

+ 23
- 35
src/oscar/apps/dashboard/pages/views.py Просмотреть файл

@@ -63,12 +63,30 @@ class PageListView(ListView):
63 63
         return context
64 64
 
65 65
 
66
-class PageCreateView(generic.CreateView):
66
+class PageCreateUpdateMixin(object):
67
+
67 68
     template_name = 'dashboard/pages/update.html'
68 69
     model = FlatPage
69 70
     form_class = forms.PageUpdateForm
70 71
     context_object_name = 'page'
71 72
 
73
+    def get_success_url(self):
74
+        msg = render_to_string('oscar/dashboard/pages/messages/saved.html',
75
+                               {'page': self.object})
76
+        messages.success(self.request, msg, extra_tags='safe noicon')
77
+        return reverse('dashboard:page-list')
78
+
79
+    def form_valid(self, form):
80
+        # Ensure saved page is added to the current site
81
+        page = form.save()
82
+        if not page.sites.exists():
83
+            page.sites.add(Site.objects.get_current())
84
+        self.object = page
85
+        return HttpResponseRedirect(self.get_success_url())
86
+
87
+
88
+class PageCreateView(PageCreateUpdateMixin, generic.CreateView):
89
+
72 90
     def get_context_data(self, **kwargs):
73 91
         ctx = super(PageCreateView, self).get_context_data(**kwargs)
74 92
         ctx['title'] = _('Create New Page')
@@ -76,9 +94,8 @@ class PageCreateView(generic.CreateView):
76 94
 
77 95
     def form_valid(self, form):
78 96
         """
79
-        Store new flatpage from form data. Checks wether a site
80
-        is specified for the flatpage or sets the current site by
81
-        default. Additionally, if URL is left blank, a slugified
97
+        Store new flatpage from form data.
98
+        Additionally, if URL is left blank, a slugified
82 99
         version of the title will be used as URL after checking
83 100
         if it is valid.
84 101
         """
@@ -93,51 +110,22 @@ class PageCreateView(generic.CreateView):
93 110
         except ValidationError:
94 111
             pass
95 112
         else:
96
-            # use current site as default for new page
97
-            page.save()
98
-            page.sites.add(Site.objects.get_current())
99
-
100
-            return HttpResponseRedirect(self.get_success_url(page))
113
+            return super(PageCreateView, self).form_valid(form)
101 114
 
102 115
         ctx = self.get_context_data()
103 116
         ctx['form'] = form
104 117
         return self.render_to_response(ctx)
105 118
 
106
-    def get_success_url(self, page):
107
-        msg = render_to_string('oscar/dashboard/pages/messages/saved.html',
108
-                               {'page': page})
109
-        messages.success(self.request, msg, extra_tags='safe noicon')
110
-        return reverse('dashboard:page-list')
111 119
 
112
-
113
-class PageUpdateView(generic.UpdateView):
120
+class PageUpdateView(PageCreateUpdateMixin, generic.UpdateView):
114 121
     """
115 122
     View for updating flatpages from the dashboard.
116 123
     """
117
-    template_name = 'dashboard/pages/update.html'
118
-    model = FlatPage
119
-    form_class = forms.PageUpdateForm
120
-    context_object_name = 'page'
121
-
122 124
     def get_context_data(self, **kwargs):
123 125
         ctx = super(PageUpdateView, self).get_context_data(**kwargs)
124 126
         ctx['title'] = self.object.title
125 127
         return ctx
126 128
 
127
-    def form_valid(self, form):
128
-        # Ensure saved page is added to the current site
129
-        page = form.save(commit=False)
130
-        if not page.sites.exists():
131
-            page.sites.add(Site.objects.get_current())
132
-        page.save()
133
-        return HttpResponseRedirect(self.get_success_url())
134
-
135
-    def get_success_url(self):
136
-        msg = render_to_string('oscar/dashboard/pages/messages/saved.html',
137
-                               {'page': self.object})
138
-        messages.success(self.request, msg, extra_tags='safe noicon')
139
-        return reverse('dashboard:page-list')
140
-
141 129
 
142 130
 class PageDeleteView(generic.DeleteView):
143 131
     template_name = 'dashboard/pages/delete.html'

+ 109
- 55
tests/functional/dashboard/test_page.py Просмотреть файл

@@ -1,5 +1,8 @@
1 1
 from django.core.urlresolvers import reverse
2 2
 from django.contrib.flatpages.models import FlatPage
3
+from django.test import TestCase
4
+
5
+from oscar.apps.dashboard.pages.forms import PageUpdateForm
3 6
 from oscar.test.testcases import WebTestCase
4 7
 
5 8
 
@@ -28,29 +31,13 @@ class TestPageDashboard(WebTestCase):
28 31
         self.assertTrue(self.flatpage_1 in objects)
29 32
         self.assertTrue(self.flatpage_2 in objects)
30 33
 
31
-    def test_doesnt_allow_existing_pages_to_be_clobbered(self):
32
-        self.assertEqual(FlatPage.objects.count(), 2)
33
-
34
-        page = self.get(reverse('dashboard:page-create'))
35
-        form = page.form
36
-        form['title'] = 'test'
37
-        form['url'] = '/dashboard/pages/'
38
-        response = form.submit()
39
-        self.assertEqual(200, response.status_code)
40
-        self.assertEqual("Specified page already exists!",
41
-                         response.context['form'].errors['url'][0])
42
-        self.assertEqual(FlatPage.objects.count(), 2)
43
-
44
-    def test_allows_page_to_be_created(self):
45
-        page = self.get(reverse('dashboard:page-create'))
46
-        form = page.form
47
-        form['title'] = 'test'
48
-        form['url'] = '/my-new-url/'
49
-        form['content'] = 'my content here'
50
-        response = form.submit()
34
+    def test_dashboard_delete_pages(self):
35
+        page = self.get(reverse('dashboard:page-list'))
36
+        delete_page = page.click(linkid="delete_page_%s" % self.flatpage_1.id)
37
+        response = delete_page.form.submit()
51 38
 
52 39
         self.assertIsRedirect(response)
53
-        self.assertEqual(FlatPage.objects.count(), 3)
40
+        self.assertEqual(FlatPage.objects.count(), 1)
54 41
 
55 42
     def test_dashboard_create_page_with_slugified_url(self):
56 43
         page = self.get(reverse('dashboard:page-create'))
@@ -60,53 +47,120 @@ class TestPageDashboard(WebTestCase):
60 47
         response = form.submit()
61 48
 
62 49
         self.assertIsRedirect(response)
63
-        self.assertEqual(FlatPage.objects.count(), 3)
64 50
 
65
-    def test_dashboard_create_page_with_exisiting_url_does_not_work(self):
51
+    def test_dashboard_create_page_with_duplicate_slugified_url_fails(self):
66 52
         page = self.get(reverse('dashboard:page-create'))
67 53
         form = page.form
68
-        form['title'] = 'test'
69
-        form['url'] = '/url1/'  # already exists
54
+        form['title'] = 'url1'  # This will slugify to url1
70 55
         form['content'] = 'my content here'
71 56
         response = form.submit()
72 57
 
73 58
         self.assertEqual(200, response.status_code)
74
-        self.assertEqual("Specified page already exists!",
75
-                         response.context['form'].errors['url'][0])
76
-        self.assertEqual(FlatPage.objects.count(), 2)
77 59
 
78
-    def test_dashboard_update_page_valid_url(self):
79
-        page = self.get(reverse('dashboard:page-update',
80
-                                kwargs={'pk': self.flatpage_1.pk}))
60
+    def test_default_site_added_for_new_pages(self):
61
+        page = self.get(reverse('dashboard:page-create'))
81 62
         form = page.form
82 63
         form['title'] = 'test'
83
-        form['url'] = '/new/url/'  # already exists
84
-        form['content'] = 'my content here'
85
-        response = form.submit()
64
+        form['url'] = '/hello-world/'
65
+        form.submit()
86 66
 
87
-        self.assertIsRedirect(response)
67
+        p = FlatPage.objects.get(url='/hello-world/')
68
+        self.assertEqual(p.sites.count(), 1)
88 69
 
89
-        page = FlatPage.objects.get(pk=self.flatpage_1.pk)
90
-        self.assertEqual(page.title, 'test')
91
-        self.assertEqual(page.url, '/new/url/')
92
-        self.assertEqual(page.content, "my content here")
93
-        self.assertEqual(page.sites.count(), 1)
94 70
 
95
-    def test_dashboard_update_page_invalid_url(self):
96
-        page = self.get(reverse('dashboard:page-update',
97
-                                kwargs={'pk': self.flatpage_1.pk}))
98
-        form = page.form
99
-        form['url'] = '/url2/'  # already exists
100
-        response = form.submit()
71
+class DashboardPageUpdateFormTestCase(TestCase):
101 72
 
102
-        self.assertEqual(200, response.status_code)
103
-        self.assertEqual("Specified page already exists!",
104
-                         response.context['form'].errors['url'][0])
73
+    def setUp(self):
74
+        self.flatpage_1 = FlatPage.objects.create(
75
+            title='title1', url='/url1/',
76
+            content='some content')
77
+        self.flatpage_2 = FlatPage.objects.create(
78
+            title='title2', url='/url2/',
79
+            content='other content')
105 80
 
106
-    def test_dashboard_delete_pages(self):
107
-        page = self.get(reverse('dashboard:page-list'))
108
-        delete_page = page.click(linkid="delete_page_%s" % self.flatpage_1.id)
109
-        response = delete_page.form.submit()
81
+    def test_doesnt_allow_existing_pages_to_be_clobbered(self):
82
+        form = PageUpdateForm(data={
83
+            'title': 'test',
84
+            'url': '/dashboard/pages/',
85
+        })
86
+        self.assertFalse(form.is_valid())
87
+        self.assertEqual(
88
+            form.errors['url'],
89
+            ['Specified page already exists!']
90
+        )
110 91
 
111
-        self.assertIsRedirect(response)
112
-        self.assertEqual(FlatPage.objects.count(), 1)
92
+    def test_allows_page_to_be_created(self):
93
+        form = PageUpdateForm(data={
94
+            'title': 'test',
95
+            'url': '/my-new-url/',
96
+            'content': 'my content here'
97
+        })
98
+
99
+        self.assertTrue(form.is_valid())
100
+        form.save()
101
+        self.assertEqual(FlatPage.objects.count(), 3)
102
+
103
+    def test_create_page_with_slugified_url(self):
104
+        form = PageUpdateForm(data={
105
+            'title': 'test',
106
+            'content': 'my content here'
107
+        })
108
+
109
+        self.assertTrue(form.is_valid())
110
+        form.save()
111
+        self.assertEqual(FlatPage.objects.count(), 3)
112
+
113
+    def test_create_page_with_existing_url_does_not_work(self):
114
+        form = PageUpdateForm(data={
115
+            'title': 'test',
116
+            'url': '/url1/',  # already exists
117
+            'content': 'my content here'
118
+        })
119
+
120
+        self.assertFalse(form.is_valid())
121
+        self.assertEqual(
122
+            form.errors['url'],
123
+            ['Specified page already exists!']
124
+        )
125
+
126
+    def test_update_page_valid_url(self):
127
+        form = PageUpdateForm(instance=self.flatpage_1, data={
128
+            'title': 'test',
129
+            'url': '/new/url/',
130
+            'content': 'my content here'
131
+        })
132
+
133
+        form.save()
134
+
135
+        self.flatpage_1.refresh_from_db()
136
+        page = self.flatpage_1
137
+        self.assertEqual(page.title, 'test')
138
+        self.assertEqual(page.url, '/new/url/')
139
+        self.assertEqual(page.content, "my content here")
140
+
141
+    def test_invalid_chars_in_url(self):
142
+        form = PageUpdateForm(data={
143
+            'url': '/%* /',
144
+            'title': 'Title',
145
+            'content': 'Content',
146
+        })
147
+
148
+        self.assertFalse(form.is_valid())
149
+        self.assertEqual(
150
+            form.errors['url'],
151
+            ['This value must contain only letters, numbers, dots, underscores, dashes, slashes or tildes.']
152
+        )
153
+
154
+    def test_invalid_url_length(self):
155
+        form = PageUpdateForm(data={
156
+            'url': '/this_url_is_more_than_100_characters_long_which_is_invalid'
157
+                   '_because_the_model_field_has_a_max_length_of_100',
158
+            'title': 'Title',
159
+            'content': 'Content',
160
+        })
161
+
162
+        self.assertFalse(form.is_valid())
163
+        self.assertEqual(
164
+            form.errors['url'],
165
+            ['Ensure this value has at most 100 characters (it has 107).']
166
+        )

Загрузка…
Отмена
Сохранить