We are currently solving the following issue within our repository. Here is the issue text:
--- BEGIN ISSUE ---
Cachebusting query parameter (e.g. _=1441835249458) not ignored by api
From the [documentation for jQuery.ajax, under "cache"](http://api.jquery.com/jquery.ajax/):

> Setting cache to false will only work correctly with HEAD and GET requests. It works by appending "_={timestamp}" to the GET parameters. The parameter is not needed for other types of requests, except in IE8 when a POST is made to a URL that has already been requested by a GET.

It seems like it's standard practice to ignore the underscore keyword. Unless I'm mistaken this is an oversight and not a disagreement on the principle of the thing.

Reproduce: make an Ajax call to any wagtail API endpoint with the cache flag set to false. Or just navigate to something like `http://localhost:8000/api/v1/pages/?type=home.HomePage&_=1441835249458`

You'll get this message: 

```
{
    "message": "query parameter is not an operation or a recognised field: _"
}
```


--- END ISSUE ---

Below are some code segments, each from a relevant file. One or more of these files may contain bugs.
--- BEGIN FILES ---
Path: `wagtail/contrib/wagtailapi/endpoints.py`
Content:
```
1 from __future__ import absolute_import
2 
3 from collections import OrderedDict
4 
5 from django.conf.urls import url
6 from django.http import Http404
7 
8 from rest_framework import status
9 from rest_framework.response import Response
10 from rest_framework.viewsets import GenericViewSet
11 
12 from wagtail.wagtailcore.models import Page
13 from wagtail.wagtailimages.models import get_image_model
14 from wagtail.wagtaildocs.models import Document
15 from wagtail.wagtailcore.utils import resolve_model_string
16 
17 from .filters import (
18     FieldsFilter, OrderingFilter, SearchFilter,
19     ChildOfFilter, DescendantOfFilter
20 )
21 from .renderers import WagtailJSONRenderer
22 from .pagination import WagtailPagination
23 from .serializers import BaseSerializer, PageSerializer, DocumentSerializer, ImageSerializer, get_serializer_class
24 from .utils import BadRequestError
25 
26 
27 class BaseAPIEndpoint(GenericViewSet):
28     renderer_classes = [WagtailJSONRenderer]
29     pagination_class = WagtailPagination
30     base_serializer_class = BaseSerializer
31     filter_classes = []
32     queryset = None  # Set on subclasses or implement `get_queryset()`.
33 
34     known_query_parameters = frozenset([
35         'limit',
36         'offset',
37         'fields',
38         'order',
39         'search',
40     ])
41     extra_api_fields = []
42     name = None  # Set on subclass.
43 
44     def listing_view(self, request):
45         queryset = self.get_queryset()
46         self.check_query_parameters(queryset)
47         queryset = self.filter_queryset(queryset)
48         queryset = self.paginate_queryset(queryset)
49         serializer = self.get_serializer(queryset, many=True)
50         return self.get_paginated_response(serializer.data)
51 
52     def detail_view(self, request, pk):
53         instance = self.get_object()
54         serializer = self.get_serializer(instance)
55         return Response(serializer.data)
56 
57     def handle_exception(self, exc):
58         if isinstance(exc, Http404):
59             data = {'message': str(exc)}
60             return Response(data, status=status.HTTP_404_NOT_FOUND)
61         elif isinstance(exc, BadRequestError):
62             data = {'message': str(exc)}
63             return Response(data, status=status.HTTP_400_BAD_REQUEST)
64         return super(BaseAPIEndpoint, self).handle_exception(exc)
65 
66     def get_api_fields(self, model):
67         """
68         This returns a list of field names that are allowed to
69         be used in the API (excluding the id field).
70         """
71         api_fields = self.extra_api_fields[:]
72 
73         if hasattr(model, 'api_fields'):
74             api_fields.extend(model.api_fields)
75 
76         return api_fields
77 
78     def check_query_parameters(self, queryset):
79         """
80         Ensure that only valid query paramters are included in the URL.
81         """
82         query_parameters = set(self.request.GET.keys())
83 
84         # All query paramters must be either a field or an operation
85         allowed_query_parameters = set(self.get_api_fields(queryset.model)).union(self.known_query_parameters).union({'id'})
86         unknown_parameters = query_parameters - allowed_query_parameters
87         if unknown_parameters:
88             raise BadRequestError("query parameter is not an operation or a recognised field: %s" % ', '.join(sorted(unknown_parameters)))
89 
90     def get_serializer_class(self):
91         request = self.request
92 
93         # Get model
94         if self.action == 'listing_view':
95             model = self.get_queryset().model
96         else:
97             model = type(self.get_object())
98 
99         # Get all available fields
100         all_fields = self.get_api_fields(model)
101         all_fields = list(OrderedDict.fromkeys(all_fields)) # Removes any duplicates in case the developer put "title" in api_fields
102 
103         if self.action == 'listing_view':
104             # Listing views just show the title field and any other allowed field the user specified
105             if 'fields' in request.GET:
106                 fields = set(request.GET['fields'].split(','))
107             else:
108                 fields = {'title'}
109 
110             unknown_fields = fields - set(all_fields)
111 
112             if unknown_fields:
113                 raise BadRequestError("unknown fields: %s" % ', '.join(sorted(unknown_fields)))
114 
115             # Reorder fields so it matches the order of all_fields
116             fields = [field for field in all_fields if field in fields]
117         else:
118             # Detail views show all fields all the time
119             fields = all_fields
120 
121         # Always show id and meta first
122         fields = ['id', 'meta'] + fields
123 
124         # If showing details, add the parent field
125         if isinstance(self, PagesAPIEndpoint) and self.get_serializer_context().get('show_details', False):
126             fields.insert(2, 'parent')
127 
128         return get_serializer_class(model, fields, base=self.base_serializer_class)
129 
130     def get_serializer_context(self):
131         """
132         The serialization context differs between listing and detail views.
133         """
134         request = self.request
135 
136         if self.action == 'listing_view':
137             return {
138                 'request': request,
139                 'view': self,
140             }
141 
142         return {
143             'request': request,
144             'view': self,
145             'show_details': True
146         }
147 
148     def get_renderer_context(self):
149         context = super(BaseAPIEndpoint, self).get_renderer_context()
150         context['endpoints'] = [
151             PagesAPIEndpoint,
152             ImagesAPIEndpoint,
153             DocumentsAPIEndpoint
154         ]
155         return context
156 
157     @classmethod
158     def get_urlpatterns(cls):
159         """
160         This returns a list of URL patterns for the endpoint
161         """
162         return [
163             url(r'^$', cls.as_view({'get': 'listing_view'}), name='listing'),
164             url(r'^(?P<pk>\d+)/$', cls.as_view({'get': 'detail_view'}), name='detail'),
165         ]
166 
167     @classmethod
168     def has_model(cls, model):
169         return NotImplemented
170 
171 
172 class PagesAPIEndpoint(BaseAPIEndpoint):
173     base_serializer_class = PageSerializer
174     filter_backends = [
175         FieldsFilter,
176         ChildOfFilter,
177         DescendantOfFilter,
178         OrderingFilter,
179         SearchFilter
180     ]
181     known_query_parameters = BaseAPIEndpoint.known_query_parameters.union([
182         'type',
183         'child_of',
184         'descendant_of',
185     ])
186     extra_api_fields = ['title']
187     name = 'pages'
188 
189     def get_queryset(self):
190         request = self.request
191 
192         # Allow pages to be filtered to a specific type
193         if 'type' not in request.GET:
194             model = Page
195         else:
196             model_name = request.GET['type']
197             try:
198                 model = resolve_model_string(model_name)
199             except LookupError:
200                 raise BadRequestError("type doesn't exist")
201             if not issubclass(model, Page):
202                 raise BadRequestError("type doesn't exist")
203 
204         # Get live pages that are not in a private section
205         queryset = model.objects.public().live()
206 
207         # Filter by site
208         queryset = queryset.descendant_of(request.site.root_page, inclusive=True)
209 
210         return queryset
211 
212     def get_object(self):
213         base = super(PagesAPIEndpoint, self).get_object()
214         return base.specific
215 
216     @classmethod
217     def has_model(cls, model):
218         return issubclass(model, Page)
219 
220 
221 class ImagesAPIEndpoint(BaseAPIEndpoint):
222     queryset = get_image_model().objects.all().order_by('id')
223     base_serializer_class = ImageSerializer
224     filter_backends = [FieldsFilter, OrderingFilter, SearchFilter]
225     extra_api_fields = ['title', 'tags', 'width', 'height']
226     name = 'images'
227 
228     @classmethod
229     def has_model(cls, model):
230         return model == get_image_model()
231 
232 
233 class DocumentsAPIEndpoint(BaseAPIEndpoint):
234     queryset = Document.objects.all().order_by('id')
235     base_serializer_class = DocumentSerializer
236     filter_backends = [FieldsFilter, OrderingFilter, SearchFilter]
237     extra_api_fields = ['title', 'tags']
238     name = 'documents'
239 
240     @classmethod
241     def has_model(cls, model):
242         return model == Document
243
```
--- END FILES ---

Please first localize the bug based on the issue statement, and then generate a patch according to the `git diff` format fenced by three backticks.

Here is an example:

```diff
diff --git a/examples/server_async.py b/examples/server_async.py
--- a/examples/server_async.py
+++ b/examples/server_async.py
@@ -313,4 +313,4 @@
 
 
 if __name__ == "__main__":
-    asyncio.run(run_async_server("."), debug=True)
+    asyncio.run(run_async_server(), debug=True)
diff --git a/examples/server_sync.py b/examples/server_sync.py
--- a/examples/server_sync.py
+++ b/examples/server_sync.py
@@ -313,5 +313,5 @@
 
 
 if __name__ == "__main__":
-    server = run_sync_server(".")
+    server = run_sync_server()
     server.shutdown()

```

diff --git a/wagtail/contrib/wagtailapi/endpoints.py b/wagtail/contrib/wagtailapi/endpoints.py
--- a/wagtail/contrib/wagtailapi/endpoints.py
+++ b/wagtail/contrib/wagtailapi/endpoints.py
@@ -37,6 +37,9 @@
         'fields',
         'order',
         'search',
+
+        # Used by jQuery for cache-busting. See #1671
+        '_',
     ])
     extra_api_fields = []
     name = None  # Set on subclass.

{"golden_diff": "diff --git a/wagtail/contrib/wagtailapi/endpoints.py b/wagtail/contrib/wagtailapi/endpoints.py\n--- a/wagtail/contrib/wagtailapi/endpoints.py\n+++ b/wagtail/contrib/wagtailapi/endpoints.py\n@@ -37,6 +37,9 @@\n         'fields',\n         'order',\n         'search',\n+\n+        # Used by jQuery for cache-busting. See #1671\n+        '_',\n     ])\n     extra_api_fields = []\n     name = None  # Set on subclass.\n", "issue": "Cachebusting query parameter (e.g. _=1441835249458) not ignored by api\nFrom the [documentation for jQuery.ajax, under \"cache\"](http://api.jquery.com/jquery.ajax/):\n\n> Setting cache to false will only work correctly with HEAD and GET requests. It works by appending \"_={timestamp}\" to the GET parameters. The parameter is not needed for other types of requests, except in IE8 when a POST is made to a URL that has already been requested by a GET.\n\nIt seems like it's standard practice to ignore the underscore keyword. Unless I'm mistaken this is an oversight and not a disagreement on the principle of the thing.\n\nReproduce: make an Ajax call to any wagtail API endpoint with the cache flag set to false. Or just navigate to something like `http://localhost:8000/api/v1/pages/?type=home.HomePage&_=1441835249458`\n\nYou'll get this message: \n\n```\n{\n    \"message\": \"query parameter is not an operation or a recognised field: _\"\n}\n```\n\n", "before_files": [{"content": "from __future__ import absolute_import\n\nfrom collections import OrderedDict\n\nfrom django.conf.urls import url\nfrom django.http import Http404\n\nfrom rest_framework import status\nfrom rest_framework.response import Response\nfrom rest_framework.viewsets import GenericViewSet\n\nfrom wagtail.wagtailcore.models import Page\nfrom wagtail.wagtailimages.models import get_image_model\nfrom wagtail.wagtaildocs.models import Document\nfrom wagtail.wagtailcore.utils import resolve_model_string\n\nfrom .filters import (\n    FieldsFilter, OrderingFilter, SearchFilter,\n    ChildOfFilter, DescendantOfFilter\n)\nfrom .renderers import WagtailJSONRenderer\nfrom .pagination import WagtailPagination\nfrom .serializers import BaseSerializer, PageSerializer, DocumentSerializer, ImageSerializer, get_serializer_class\nfrom .utils import BadRequestError\n\n\nclass BaseAPIEndpoint(GenericViewSet):\n    renderer_classes = [WagtailJSONRenderer]\n    pagination_class = WagtailPagination\n    base_serializer_class = BaseSerializer\n    filter_classes = []\n    queryset = None  # Set on subclasses or implement `get_queryset()`.\n\n    known_query_parameters = frozenset([\n        'limit',\n        'offset',\n        'fields',\n        'order',\n        'search',\n    ])\n    extra_api_fields = []\n    name = None  # Set on subclass.\n\n    def listing_view(self, request):\n        queryset = self.get_queryset()\n        self.check_query_parameters(queryset)\n        queryset = self.filter_queryset(queryset)\n        queryset = self.paginate_queryset(queryset)\n        serializer = self.get_serializer(queryset, many=True)\n        return self.get_paginated_response(serializer.data)\n\n    def detail_view(self, request, pk):\n        instance = self.get_object()\n        serializer = self.get_serializer(instance)\n        return Response(serializer.data)\n\n    def handle_exception(self, exc):\n        if isinstance(exc, Http404):\n            data = {'message': str(exc)}\n            return Response(data, status=status.HTTP_404_NOT_FOUND)\n        elif isinstance(exc, BadRequestError):\n            data = {'message': str(exc)}\n            return Response(data, status=status.HTTP_400_BAD_REQUEST)\n        return super(BaseAPIEndpoint, self).handle_exception(exc)\n\n    def get_api_fields(self, model):\n        \"\"\"\n        This returns a list of field names that are allowed to\n        be used in the API (excluding the id field).\n        \"\"\"\n        api_fields = self.extra_api_fields[:]\n\n        if hasattr(model, 'api_fields'):\n            api_fields.extend(model.api_fields)\n\n        return api_fields\n\n    def check_query_parameters(self, queryset):\n        \"\"\"\n        Ensure that only valid query paramters are included in the URL.\n        \"\"\"\n        query_parameters = set(self.request.GET.keys())\n\n        # All query paramters must be either a field or an operation\n        allowed_query_parameters = set(self.get_api_fields(queryset.model)).union(self.known_query_parameters).union({'id'})\n        unknown_parameters = query_parameters - allowed_query_parameters\n        if unknown_parameters:\n            raise BadRequestError(\"query parameter is not an operation or a recognised field: %s\" % ', '.join(sorted(unknown_parameters)))\n\n    def get_serializer_class(self):\n        request = self.request\n\n        # Get model\n        if self.action == 'listing_view':\n            model = self.get_queryset().model\n        else:\n            model = type(self.get_object())\n\n        # Get all available fields\n        all_fields = self.get_api_fields(model)\n        all_fields = list(OrderedDict.fromkeys(all_fields)) # Removes any duplicates in case the developer put \"title\" in api_fields\n\n        if self.action == 'listing_view':\n            # Listing views just show the title field and any other allowed field the user specified\n            if 'fields' in request.GET:\n                fields = set(request.GET['fields'].split(','))\n            else:\n                fields = {'title'}\n\n            unknown_fields = fields - set(all_fields)\n\n            if unknown_fields:\n                raise BadRequestError(\"unknown fields: %s\" % ', '.join(sorted(unknown_fields)))\n\n            # Reorder fields so it matches the order of all_fields\n            fields = [field for field in all_fields if field in fields]\n        else:\n            # Detail views show all fields all the time\n            fields = all_fields\n\n        # Always show id and meta first\n        fields = ['id', 'meta'] + fields\n\n        # If showing details, add the parent field\n        if isinstance(self, PagesAPIEndpoint) and self.get_serializer_context().get('show_details', False):\n            fields.insert(2, 'parent')\n\n        return get_serializer_class(model, fields, base=self.base_serializer_class)\n\n    def get_serializer_context(self):\n        \"\"\"\n        The serialization context differs between listing and detail views.\n        \"\"\"\n        request = self.request\n\n        if self.action == 'listing_view':\n            return {\n                'request': request,\n                'view': self,\n            }\n\n        return {\n            'request': request,\n            'view': self,\n            'show_details': True\n        }\n\n    def get_renderer_context(self):\n        context = super(BaseAPIEndpoint, self).get_renderer_context()\n        context['endpoints'] = [\n            PagesAPIEndpoint,\n            ImagesAPIEndpoint,\n            DocumentsAPIEndpoint\n        ]\n        return context\n\n    @classmethod\n    def get_urlpatterns(cls):\n        \"\"\"\n        This returns a list of URL patterns for the endpoint\n        \"\"\"\n        return [\n            url(r'^$', cls.as_view({'get': 'listing_view'}), name='listing'),\n            url(r'^(?P<pk>\\d+)/$', cls.as_view({'get': 'detail_view'}), name='detail'),\n        ]\n\n    @classmethod\n    def has_model(cls, model):\n        return NotImplemented\n\n\nclass PagesAPIEndpoint(BaseAPIEndpoint):\n    base_serializer_class = PageSerializer\n    filter_backends = [\n        FieldsFilter,\n        ChildOfFilter,\n        DescendantOfFilter,\n        OrderingFilter,\n        SearchFilter\n    ]\n    known_query_parameters = BaseAPIEndpoint.known_query_parameters.union([\n        'type',\n        'child_of',\n        'descendant_of',\n    ])\n    extra_api_fields = ['title']\n    name = 'pages'\n\n    def get_queryset(self):\n        request = self.request\n\n        # Allow pages to be filtered to a specific type\n        if 'type' not in request.GET:\n            model = Page\n        else:\n            model_name = request.GET['type']\n            try:\n                model = resolve_model_string(model_name)\n            except LookupError:\n                raise BadRequestError(\"type doesn't exist\")\n            if not issubclass(model, Page):\n                raise BadRequestError(\"type doesn't exist\")\n\n        # Get live pages that are not in a private section\n        queryset = model.objects.public().live()\n\n        # Filter by site\n        queryset = queryset.descendant_of(request.site.root_page, inclusive=True)\n\n        return queryset\n\n    def get_object(self):\n        base = super(PagesAPIEndpoint, self).get_object()\n        return base.specific\n\n    @classmethod\n    def has_model(cls, model):\n        return issubclass(model, Page)\n\n\nclass ImagesAPIEndpoint(BaseAPIEndpoint):\n    queryset = get_image_model().objects.all().order_by('id')\n    base_serializer_class = ImageSerializer\n    filter_backends = [FieldsFilter, OrderingFilter, SearchFilter]\n    extra_api_fields = ['title', 'tags', 'width', 'height']\n    name = 'images'\n\n    @classmethod\n    def has_model(cls, model):\n        return model == get_image_model()\n\n\nclass DocumentsAPIEndpoint(BaseAPIEndpoint):\n    queryset = Document.objects.all().order_by('id')\n    base_serializer_class = DocumentSerializer\n    filter_backends = [FieldsFilter, OrderingFilter, SearchFilter]\n    extra_api_fields = ['title', 'tags']\n    name = 'documents'\n\n    @classmethod\n    def has_model(cls, model):\n        return model == Document\n", "path": "wagtail/contrib/wagtailapi/endpoints.py"}], "after_files": [{"content": "from __future__ import absolute_import\n\nfrom collections import OrderedDict\n\nfrom django.conf.urls import url\nfrom django.http import Http404\n\nfrom rest_framework import status\nfrom rest_framework.response import Response\nfrom rest_framework.viewsets import GenericViewSet\n\nfrom wagtail.wagtailcore.models import Page\nfrom wagtail.wagtailimages.models import get_image_model\nfrom wagtail.wagtaildocs.models import Document\nfrom wagtail.wagtailcore.utils import resolve_model_string\n\nfrom .filters import (\n    FieldsFilter, OrderingFilter, SearchFilter,\n    ChildOfFilter, DescendantOfFilter\n)\nfrom .renderers import WagtailJSONRenderer\nfrom .pagination import WagtailPagination\nfrom .serializers import BaseSerializer, PageSerializer, DocumentSerializer, ImageSerializer, get_serializer_class\nfrom .utils import BadRequestError\n\n\nclass BaseAPIEndpoint(GenericViewSet):\n    renderer_classes = [WagtailJSONRenderer]\n    pagination_class = WagtailPagination\n    base_serializer_class = BaseSerializer\n    filter_classes = []\n    queryset = None  # Set on subclasses or implement `get_queryset()`.\n\n    known_query_parameters = frozenset([\n        'limit',\n        'offset',\n        'fields',\n        'order',\n        'search',\n\n        # Used by jQuery for cache-busting. See #1671\n        '_',\n    ])\n    extra_api_fields = []\n    name = None  # Set on subclass.\n\n    def listing_view(self, request):\n        queryset = self.get_queryset()\n        self.check_query_parameters(queryset)\n        queryset = self.filter_queryset(queryset)\n        queryset = self.paginate_queryset(queryset)\n        serializer = self.get_serializer(queryset, many=True)\n        return self.get_paginated_response(serializer.data)\n\n    def detail_view(self, request, pk):\n        instance = self.get_object()\n        serializer = self.get_serializer(instance)\n        return Response(serializer.data)\n\n    def handle_exception(self, exc):\n        if isinstance(exc, Http404):\n            data = {'message': str(exc)}\n            return Response(data, status=status.HTTP_404_NOT_FOUND)\n        elif isinstance(exc, BadRequestError):\n            data = {'message': str(exc)}\n            return Response(data, status=status.HTTP_400_BAD_REQUEST)\n        return super(BaseAPIEndpoint, self).handle_exception(exc)\n\n    def get_api_fields(self, model):\n        \"\"\"\n        This returns a list of field names that are allowed to\n        be used in the API (excluding the id field).\n        \"\"\"\n        api_fields = self.extra_api_fields[:]\n\n        if hasattr(model, 'api_fields'):\n            api_fields.extend(model.api_fields)\n\n        return api_fields\n\n    def check_query_parameters(self, queryset):\n        \"\"\"\n        Ensure that only valid query paramters are included in the URL.\n        \"\"\"\n        query_parameters = set(self.request.GET.keys())\n\n        # All query paramters must be either a field or an operation\n        allowed_query_parameters = set(self.get_api_fields(queryset.model)).union(self.known_query_parameters).union({'id'})\n        unknown_parameters = query_parameters - allowed_query_parameters\n        if unknown_parameters:\n            raise BadRequestError(\"query parameter is not an operation or a recognised field: %s\" % ', '.join(sorted(unknown_parameters)))\n\n    def get_serializer_class(self):\n        request = self.request\n\n        # Get model\n        if self.action == 'listing_view':\n            model = self.get_queryset().model\n        else:\n            model = type(self.get_object())\n\n        # Get all available fields\n        all_fields = self.get_api_fields(model)\n        all_fields = list(OrderedDict.fromkeys(all_fields)) # Removes any duplicates in case the developer put \"title\" in api_fields\n\n        if self.action == 'listing_view':\n            # Listing views just show the title field and any other allowed field the user specified\n            if 'fields' in request.GET:\n                fields = set(request.GET['fields'].split(','))\n            else:\n                fields = {'title'}\n\n            unknown_fields = fields - set(all_fields)\n\n            if unknown_fields:\n                raise BadRequestError(\"unknown fields: %s\" % ', '.join(sorted(unknown_fields)))\n\n            # Reorder fields so it matches the order of all_fields\n            fields = [field for field in all_fields if field in fields]\n        else:\n            # Detail views show all fields all the time\n            fields = all_fields\n\n        # Always show id and meta first\n        fields = ['id', 'meta'] + fields\n\n        # If showing details, add the parent field\n        if isinstance(self, PagesAPIEndpoint) and self.get_serializer_context().get('show_details', False):\n            fields.insert(2, 'parent')\n\n        return get_serializer_class(model, fields, base=self.base_serializer_class)\n\n    def get_serializer_context(self):\n        \"\"\"\n        The serialization context differs between listing and detail views.\n        \"\"\"\n        request = self.request\n\n        if self.action == 'listing_view':\n            return {\n                'request': request,\n                'view': self,\n            }\n\n        return {\n            'request': request,\n            'view': self,\n            'show_details': True\n        }\n\n    def get_renderer_context(self):\n        context = super(BaseAPIEndpoint, self).get_renderer_context()\n        context['endpoints'] = [\n            PagesAPIEndpoint,\n            ImagesAPIEndpoint,\n            DocumentsAPIEndpoint\n        ]\n        return context\n\n    @classmethod\n    def get_urlpatterns(cls):\n        \"\"\"\n        This returns a list of URL patterns for the endpoint\n        \"\"\"\n        return [\n            url(r'^$', cls.as_view({'get': 'listing_view'}), name='listing'),\n            url(r'^(?P<pk>\\d+)/$', cls.as_view({'get': 'detail_view'}), name='detail'),\n        ]\n\n    @classmethod\n    def has_model(cls, model):\n        return NotImplemented\n\n\nclass PagesAPIEndpoint(BaseAPIEndpoint):\n    base_serializer_class = PageSerializer\n    filter_backends = [\n        FieldsFilter,\n        ChildOfFilter,\n        DescendantOfFilter,\n        OrderingFilter,\n        SearchFilter\n    ]\n    known_query_parameters = BaseAPIEndpoint.known_query_parameters.union([\n        'type',\n        'child_of',\n        'descendant_of',\n    ])\n    extra_api_fields = ['title']\n    name = 'pages'\n\n    def get_queryset(self):\n        request = self.request\n\n        # Allow pages to be filtered to a specific type\n        if 'type' not in request.GET:\n            model = Page\n        else:\n            model_name = request.GET['type']\n            try:\n                model = resolve_model_string(model_name)\n            except LookupError:\n                raise BadRequestError(\"type doesn't exist\")\n            if not issubclass(model, Page):\n                raise BadRequestError(\"type doesn't exist\")\n\n        # Get live pages that are not in a private section\n        queryset = model.objects.public().live()\n\n        # Filter by site\n        queryset = queryset.descendant_of(request.site.root_page, inclusive=True)\n\n        return queryset\n\n    def get_object(self):\n        base = super(PagesAPIEndpoint, self).get_object()\n        return base.specific\n\n    @classmethod\n    def has_model(cls, model):\n        return issubclass(model, Page)\n\n\nclass ImagesAPIEndpoint(BaseAPIEndpoint):\n    queryset = get_image_model().objects.all().order_by('id')\n    base_serializer_class = ImageSerializer\n    filter_backends = [FieldsFilter, OrderingFilter, SearchFilter]\n    extra_api_fields = ['title', 'tags', 'width', 'height']\n    name = 'images'\n\n    @classmethod\n    def has_model(cls, model):\n        return model == get_image_model()\n\n\nclass DocumentsAPIEndpoint(BaseAPIEndpoint):\n    queryset = Document.objects.all().order_by('id')\n    base_serializer_class = DocumentSerializer\n    filter_backends = [FieldsFilter, OrderingFilter, SearchFilter]\n    extra_api_fields = ['title', 'tags']\n    name = 'documents'\n\n    @classmethod\n    def has_model(cls, model):\n        return model == Document\n", "path": "wagtail/contrib/wagtailapi/endpoints.py"}]}