Bläddra i källkod

Fix handling of InvalidPage exceptions in CatalogueView (#3505)

master
Alexander Gaevsky 3 år sedan
förälder
incheckning
c87a4d88ca
Inget konto är kopplat till bidragsgivarens mejladress

+ 5
- 3
src/oscar/apps/catalogue/search_handlers.py Visa fil

2
 from django.utils.module_loading import import_string
2
 from django.utils.module_loading import import_string
3
 from django.views.generic.list import MultipleObjectMixin
3
 from django.views.generic.list import MultipleObjectMixin
4
 
4
 
5
-from oscar.core.loading import get_class, get_model
5
+from oscar.core.loading import get_class, get_classes, get_model
6
 
6
 
7
 BrowseCategoryForm = get_class('search.forms', 'BrowseCategoryForm')
7
 BrowseCategoryForm = get_class('search.forms', 'BrowseCategoryForm')
8
-SearchHandler = get_class('search.search_handlers', 'SearchHandler')
8
+SearchResultsPaginationMixin, SearchHandler = get_classes(
9
+    'search.search_handlers', ('SearchHandler', 'SearchResultsPaginationMixin'))
9
 is_solr_supported = get_class('search.features', 'is_solr_supported')
10
 is_solr_supported = get_class('search.features', 'is_solr_supported')
10
 is_elasticsearch_supported = get_class('search.features', 'is_elasticsearch_supported')
11
 is_elasticsearch_supported = get_class('search.features', 'is_elasticsearch_supported')
11
 Product = get_model('catalogue', 'Product')
12
 Product = get_model('catalogue', 'Product')
77
         return sqs
78
         return sqs
78
 
79
 
79
 
80
 
80
-class SimpleProductSearchHandler(MultipleObjectMixin):
81
+class SimpleProductSearchHandler(SearchResultsPaginationMixin, MultipleObjectMixin):
81
     """
82
     """
82
     A basic implementation of the full-featured SearchHandler that has no
83
     A basic implementation of the full-featured SearchHandler that has no
83
     faceting support, but doesn't require a Haystack backend. It only
84
     faceting support, but doesn't require a Haystack backend. It only
89
     paginate_by = settings.OSCAR_PRODUCTS_PER_PAGE
90
     paginate_by = settings.OSCAR_PRODUCTS_PER_PAGE
90
 
91
 
91
     def __init__(self, request_data, full_path, categories=None):
92
     def __init__(self, request_data, full_path, categories=None):
93
+        self.request_data = request_data
92
         self.categories = categories
94
         self.categories = categories
93
         self.kwargs = {'page': request_data.get('page') or 1}
95
         self.kwargs = {'page': request_data.get('page') or 1}
94
         self.object_list = self.get_queryset()
96
         self.object_list = self.get_queryset()

+ 4
- 2
src/oscar/apps/catalogue/views.py Visa fil

130
         try:
130
         try:
131
             self.search_handler = self.get_search_handler(
131
             self.search_handler = self.get_search_handler(
132
                 self.request.GET, request.get_full_path(), [])
132
                 self.request.GET, request.get_full_path(), [])
133
+            response = super().get(request, *args, **kwargs)
133
         except InvalidPage:
134
         except InvalidPage:
134
             # Redirect to page one.
135
             # Redirect to page one.
135
             messages.error(request, _('The given page number was invalid.'))
136
             messages.error(request, _('The given page number was invalid.'))
136
             return redirect('catalogue:index')
137
             return redirect('catalogue:index')
137
-        return super().get(request, *args, **kwargs)
138
+        return response
138
 
139
 
139
     def get_search_handler(self, *args, **kwargs):
140
     def get_search_handler(self, *args, **kwargs):
140
         return get_product_search_handler_class()(*args, **kwargs)
141
         return get_product_search_handler_class()(*args, **kwargs)
172
         try:
173
         try:
173
             self.search_handler = self.get_search_handler(
174
             self.search_handler = self.get_search_handler(
174
                 request.GET, request.get_full_path(), self.get_categories())
175
                 request.GET, request.get_full_path(), self.get_categories())
176
+            response = super().get(request, *args, **kwargs)
175
         except InvalidPage:
177
         except InvalidPage:
176
             messages.error(request, _('The given page number was invalid.'))
178
             messages.error(request, _('The given page number was invalid.'))
177
             return redirect(self.category.get_absolute_url())
179
             return redirect(self.category.get_absolute_url())
178
 
180
 
179
-        return super().get(request, *args, **kwargs)
181
+        return response
180
 
182
 
181
     def is_viewable(self, category, request):
183
     def is_viewable(self, category, request):
182
         return category.is_public or request.user.is_staff
184
         return category.is_public or request.user.is_staff

+ 34
- 35
src/oscar/apps/search/search_handlers.py Visa fil

1
 from django.core.paginator import InvalidPage, Paginator
1
 from django.core.paginator import InvalidPage, Paginator
2
-from django.utils.translation import gettext_lazy as _
3
 from haystack import connections
2
 from haystack import connections
4
 
3
 
5
 from oscar.core.loading import get_class
4
 from oscar.core.loading import get_class
9
 FacetMunger = get_class('search.facets', 'FacetMunger')
8
 FacetMunger = get_class('search.facets', 'FacetMunger')
10
 
9
 
11
 
10
 
12
-class SearchHandler(object):
11
+class SearchResultsPaginationMixin:
12
+    paginate_by = None
13
+    paginator_class = Paginator
14
+    page_kwarg = 'page'
15
+
16
+    def paginate_queryset(self, queryset, page_size):
17
+        """
18
+        Paginate the search results. This is a simplified version of
19
+        Django's MultipleObjectMixin.paginate_queryset
20
+        """
21
+        paginator = self.get_paginator(queryset, page_size)
22
+        page_kwarg = self.page_kwarg
23
+        page_number = self.request_data.get(page_kwarg, 1)
24
+        try:
25
+            page_number = int(page_number)
26
+        except ValueError:
27
+            if page_number == 'last':
28
+                page_number = paginator.num_pages
29
+            else:
30
+                raise InvalidPage
31
+        # This can also raise an InvalidPage exception.
32
+        page = paginator.page(page_number)
33
+        return paginator, page, page.object_list, page.has_other_pages()
34
+
35
+    def get_paginator(self, queryset, per_page=None):
36
+        """
37
+        Return a paginator. Override this to set settings like orphans,
38
+        allow_empty, etc.
39
+        """
40
+        return self.paginator_class(queryset, per_page)
41
+
42
+
43
+class SearchHandler(SearchResultsPaginationMixin):
13
     """
44
     """
14
     A class that is concerned with performing a search and paginating the
45
     A class that is concerned with performing a search and paginating the
15
     results. The search is triggered upon initialisation (mainly to have a
46
     results. The search is triggered upon initialisation (mainly to have a
36
 
67
 
37
     form_class = None
68
     form_class = None
38
     model_whitelist = None
69
     model_whitelist = None
39
-    paginate_by = None
40
-    paginator_class = Paginator
41
-    page_kwarg = 'page'
42
 
70
 
43
     def __init__(self, request_data, full_path):
71
     def __init__(self, request_data, full_path):
44
         self.full_path = full_path
72
         self.full_path = full_path
51
         self.results = self.get_search_results(self.search_form)
79
         self.results = self.get_search_results(self.search_form)
52
         # If below raises an UnicodeDecodeError, you're running pysolr < 3.2
80
         # If below raises an UnicodeDecodeError, you're running pysolr < 3.2
53
         # with Solr 4.
81
         # with Solr 4.
54
-        self.paginator, self.page = self.paginate_queryset(
55
-            self.results, request_data)
82
+        self.paginator, self.page = self.paginate_queryset(self.results, self.paginate_by)[0:2]
56
 
83
 
57
     # Search related methods
84
     # Search related methods
58
 
85
 
85
             sqs = sqs.models(*self.model_whitelist)
112
             sqs = sqs.models(*self.model_whitelist)
86
         return sqs
113
         return sqs
87
 
114
 
88
-    # Pagination related methods
89
-
90
-    def paginate_queryset(self, queryset, request_data):
91
-        """
92
-        Paginate the search results. This is a simplified version of
93
-        Django's MultipleObjectMixin.paginate_queryset
94
-        """
95
-        paginator = self.get_paginator(queryset)
96
-        page_kwarg = self.page_kwarg
97
-        page = request_data.get(page_kwarg, 1)
98
-        try:
99
-            page_number = int(page)
100
-        except ValueError:
101
-            if page == 'last':
102
-                page_number = paginator.num_pages
103
-            else:
104
-                raise InvalidPage(_(
105
-                    "Page is not 'last', nor can it be converted to an int."))
106
-        # This can also raise an InvalidPage exception.
107
-        return paginator, paginator.page(page_number)
108
-
109
-    def get_paginator(self, queryset):
110
-        """
111
-        Return a paginator. Override this to set settings like orphans,
112
-        allow_empty, etc.
113
-        """
114
-        return self.paginator_class(queryset, self.paginate_by)
115
-
116
     # Accessing the search results and meta data
115
     # Accessing the search results and meta data
117
 
116
 
118
     def bulk_fetch_results(self, paginated_results):
117
     def bulk_fetch_results(self, paginated_results):

+ 7
- 0
tests/functional/catalogue/test_catalogue.py Visa fil

103
         products_on_page = list(page.context['products'].all())
103
         products_on_page = list(page.context['products'].all())
104
         self.assertEqual(products_on_page, [])
104
         self.assertEqual(products_on_page, [])
105
 
105
 
106
+    def test_invalid_page_redirects_to_index(self):
107
+        create_product()
108
+        products_list_url = reverse('catalogue:index')
109
+        response = self.app.get('%s?page=200' % products_list_url)
110
+        self.assertEqual(response.status_code, 302)
111
+        self.assertRedirectsTo(response, 'catalogue:index')
112
+
106
 
113
 
107
 class TestProductCategoryView(WebTestCase):
114
 class TestProductCategoryView(WebTestCase):
108
 
115
 

Laddar…
Avbryt
Spara