Repository: wagtail/wagtail

Issue:
Attribute error in listing_view endpoint 
### Issue Summary

I get the following exception

```
Internal Server Error: /cms/api/v2beta/pages/
Traceback (most recent call last):
  File "/Users/thomas/virtualenvs/djangoenv/lib/python3.5/site-packages/django/core/handlers/exception.py", line 41, in inner
    response = get_response(request)
  File "/Users/thomas/virtualenvs/djangoenv/lib/python3.5/site-packages/django/core/handlers/base.py", line 249, in _legacy_get_response
    response = self._get_response(request)
  File "/Users/thomas/virtualenvs/djangoenv/lib/python3.5/site-packages/django/core/handlers/base.py", line 187, in _get_response
    response = self.process_exception_by_middleware(e, request)
  File "/Users/thomas/virtualenvs/djangoenv/lib/python3.5/site-packages/django/core/handlers/base.py", line 185, in _get_response
    response = wrapped_callback(request, *callback_args, **callback_kwargs)
  File "/Users/thomas/virtualenvs/djangoenv/lib/python3.5/site-packages/django/views/decorators/cache.py", line 43, in _cache_controlled
    response = viewfunc(request, *args, **kw)
  File "/Users/thomas/virtualenvs/djangoenv/lib/python3.5/site-packages/wagtail/wagtailadmin/urls/__init__.py", line 96, in wrapper
    return view_func(request, *args, **kwargs)
  File "/Users/thomas/virtualenvs/djangoenv/lib/python3.5/site-packages/wagtail/wagtailadmin/decorators.py", line 31, in decorated_view
    return view_func(request, *args, **kwargs)
  File "/Users/thomas/virtualenvs/djangoenv/lib/python3.5/site-packages/wagtail/api/v2/router.py", line 65, in wrapped
    return func(request, *args, **kwargs)
  File "/Users/thomas/virtualenvs/djangoenv/lib/python3.5/site-packages/django/views/decorators/csrf.py", line 58, in wrapped_view
    return view_func(*args, **kwargs)
  File "/Users/thomas/virtualenvs/djangoenv/lib/python3.5/site-packages/rest_framework/viewsets.py", line 90, in view
    return self.dispatch(request, *args, **kwargs)
  File "/Users/thomas/virtualenvs/djangoenv/lib/python3.5/site-packages/rest_framework/views.py", line 489, in dispatch
    response = self.handle_exception(exc)
  File "/Users/thomas/virtualenvs/djangoenv/lib/python3.5/site-packages/wagtail/api/v2/endpoints.py", line 95, in handle_exception
    return super(BaseAPIEndpoint, self).handle_exception(exc)
  File "/Users/thomas/virtualenvs/djangoenv/lib/python3.5/site-packages/rest_framework/views.py", line 449, in handle_exception
    self.raise_uncaught_exception(exc)
  File "/Users/thomas/virtualenvs/djangoenv/lib/python3.5/site-packages/rest_framework/views.py", line 486, in dispatch
    response = handler(request, *args, **kwargs)
  File "/Users/thomas/virtualenvs/djangoenv/lib/python3.5/site-packages/wagtail/wagtailadmin/api/endpoints.py", line 93, in listing_view
    response = super(PagesAdminAPIEndpoint, self).listing_view(request)
  File "/Users/thomas/virtualenvs/djangoenv/lib/python3.5/site-packages/wagtail/api/v2/endpoints.py", line 81, in listing_view
    return self.get_paginated_response(serializer.data)
  File "/Users/thomas/virtualenvs/djangoenv/lib/python3.5/site-packages/rest_framework/serializers.py", line 739, in data
    ret = super(ListSerializer, self).data
  File "/Users/thomas/virtualenvs/djangoenv/lib/python3.5/site-packages/rest_framework/serializers.py", line 263, in data
    self._data = self.to_representation(self.instance)
  File "/Users/thomas/virtualenvs/djangoenv/lib/python3.5/site-packages/rest_framework/serializers.py", line 657, in to_representation
    self.child.to_representation(item) for item in iterable
  File "/Users/thomas/virtualenvs/djangoenv/lib/python3.5/site-packages/rest_framework/serializers.py", line 657, in <listcomp>
    self.child.to_representation(item) for item in iterable
  File "/Users/thomas/virtualenvs/djangoenv/lib/python3.5/site-packages/wagtail/api/v2/serializers.py", line 275, in to_representation
    attribute = field.get_attribute(instance)
  File "/Users/thomas/virtualenvs/djangoenv/lib/python3.5/site-packages/wagtail/api/v2/serializers.py", line 47, in get_attribute
    url = get_object_detail_url(self.context, type(instance), instance.pk)
  File "/Users/thomas/virtualenvs/djangoenv/lib/python3.5/site-packages/wagtail/api/v2/serializers.py", line 20, in get_object_detail_url
    return get_full_url(context['request'], url_path)
  File "/Users/thomas/virtualenvs/djangoenv/lib/python3.5/site-packages/wagtail/api/v2/utils.py", line 25, in get_full_url
    base_url = get_base_url(request) or ''
  File "/Users/thomas/virtualenvs/djangoenv/lib/python3.5/site-packages/wagtail/api/v2/utils.py", line 15, in get_base_url
    base_url = getattr(settings, 'WAGTAILAPI_BASE_URL', request.site.root_url if request else None)
AttributeError: 'NoneType' object has no attribute 'root_url'
```

when clicking on the pages section in the backend.

### Steps to Reproduce

1. Existing django 1.11 installation
2. Added all the django apps and middleware entries in `settings.py` and all the urls as described in http://docs.wagtail.io/en/v1.13/getting_started/integrating_into_django.html
3. Created a new app via `manage.py startapp home`
4. Added simple page type as described in the docs:
```
class HomePage(Page):
    body = RichTextField(blank=True)

    content_panels = Page.content_panels + [
        FieldPanel('body', classname="full"),
    ]
```
5. Applied migrations
6. Everything worked as expected.
7. Added a new "HomePage" page as a subpage of the predefined sample page
8. This also worked.
9. Moved new page to the toplevel (subpage of root)
10. Removed the sample page
11. Then I got the described error

Everything is done local with the django builtin webserver.

### Technical details

* Python version:3.5.0
* Django version: 1.11
* Wagtail version: 1.13
* djangorestframework: 3.6.4




Discussion:
I tried to check where the request object is created. This seems to be inside of the rest_framework. Did not found any `site` attribute, that is added to the request there.
Hi @thorink,
`request.site` is defined by the middleware class `wagtail.wagtailcore.middleware.SiteMiddleware`. If this is returning `None`, it probably means you don't have a site record set up within Settings -> Sites.

In any case, it looks like this is indeed a bug - the line `base_url = getattr(settings, 'WAGTAILAPI_BASE_URL', request.site.root_url if request else None)` is failing to consider the possibility of `request.site` being defined but set to None. Thanks for the report!
Thanks, adding a site worked for me. And also thanks for working on this project in general! I keep track of this project since over a year now and really like it. No I have the opportunity to use it in one of my project.

Resolving patch:
diff --git a/wagtail/api/v2/endpoints.py b/wagtail/api/v2/endpoints.py
--- a/wagtail/api/v2/endpoints.py
+++ b/wagtail/api/v2/endpoints.py
@@ -396,7 +396,11 @@ def get_queryset(self):
         queryset = queryset.public().live()
 
         # Filter by site
-        queryset = queryset.descendant_of(request.site.root_page, inclusive=True)
+        if request.site:
+            queryset = queryset.descendant_of(request.site.root_page, inclusive=True)
+        else:
+            # No sites configured
+            queryset = queryset.none()
 
         return queryset
 
diff --git a/wagtail/api/v2/utils.py b/wagtail/api/v2/utils.py
--- a/wagtail/api/v2/utils.py
+++ b/wagtail/api/v2/utils.py
@@ -12,7 +12,7 @@ class BadRequestError(Exception):
 
 
 def get_base_url(request=None):
-    base_url = getattr(settings, 'WAGTAILAPI_BASE_URL', request.site.root_url if request else None)
+    base_url = getattr(settings, 'WAGTAILAPI_BASE_URL', request.site.root_url if request and request.site else None)
 
     if base_url:
         # We only want the scheme and netloc


Test patch:
diff --git a/wagtail/api/v2/tests/test_pages.py b/wagtail/api/v2/tests/test_pages.py
--- a/wagtail/api/v2/tests/test_pages.py
+++ b/wagtail/api/v2/tests/test_pages.py
@@ -11,7 +11,7 @@
 from wagtail.api.v2 import signal_handlers
 from wagtail.tests.demosite import models
 from wagtail.tests.testapp.models import StreamPage
-from wagtail.wagtailcore.models import Page
+from wagtail.wagtailcore.models import Page, Site
 
 
 def get_total_page_count():
@@ -753,6 +753,14 @@ def test_empty_searches_work(self):
         self.assertEqual(response['Content-type'], 'application/json')
         self.assertEqual(content['meta']['total_count'], 0)
 
+    # REGRESSION TESTS
+
+    def test_issue_3967(self):
+        # The API crashed whenever the listing view was called without a site configured
+        Site.objects.all().delete()
+        response = self.get_response()
+        self.assertEqual(response.status_code, 200)
+
 
 class TestPageDetail(TestCase):
     fixtures = ['demosite.json']