Repository: wagtail/wagtail

Issue:
API doesn't support StreamField
Here is the traceback:

```
Traceback:
File "/home/lee/Code/cms/venv/lib/python3.4/site-packages/django/core/handlers/base.py" in get_response
  111.                     response = wrapped_callback(request, *callback_args, **callback_kwargs)
File "/home/lee/Code/cms/venv/lib/python3.4/site-packages/wagtail/contrib/wagtailapi/api.py" in wrapper
  69.                 return self.make_response(request, view(request, *args, **kwargs))
File "/home/lee/Code/cms/venv/lib/python3.4/site-packages/wagtail/contrib/wagtailapi/api.py" in make_response
  54.             json.dumps(data, indent=4, cls=WagtailAPIJSONEncoder),
File "/home/lee/.pyenv/versions/3.4.2/lib/python3.4/json/__init__.py" in dumps
  237.         **kw).encode(obj)
File "/home/lee/.pyenv/versions/3.4.2/lib/python3.4/json/encoder.py" in encode
  194.             chunks = list(chunks)
File "/home/lee/.pyenv/versions/3.4.2/lib/python3.4/json/encoder.py" in _iterencode
  422.             yield from _iterencode_dict(o, _current_indent_level)
File "/home/lee/.pyenv/versions/3.4.2/lib/python3.4/json/encoder.py" in _iterencode_dict
  396.                 yield from chunks
File "/home/lee/.pyenv/versions/3.4.2/lib/python3.4/json/encoder.py" in _iterencode
  429.             o = _default(o)
File "/home/lee/Code/cms/venv/lib/python3.4/site-packages/wagtail/contrib/wagtailapi/api.py" in default
  51.                     return super(WagtailAPIJSONEncoder, self).default(o)
File "/home/lee/Code/cms/venv/lib/python3.4/site-packages/django/core/serializers/json.py" in default
  109.             return super(DjangoJSONEncoder, self).default(o)
File "/home/lee/.pyenv/versions/3.4.2/lib/python3.4/json/encoder.py" in default
  173.         raise TypeError(repr(o) + " is not JSON serializable")

Exception Type: TypeError at /api/v1/pages/6/
Exception Value: [<wagtail.wagtailcore.blocks.stream_block.StreamValue.StreamChild object at 0x7f35564b07b8>, <wagtail.wagtailcore.blocks.stream_block.StreamValue.StreamChild object at 0x7f35564b07f0>, <wagtail.wagtailcore.blocks.stream_block.StreamValue.StreamChild object at 0x7f35564b0208>] is not JSON serializable
```



Discussion:
Thanks for reporting! Streamfield and the API were developed at the same time and no integration was made at the time. Probably one to fix before the release


Resolving patch:
diff --git a/wagtail/contrib/wagtailapi/api.py b/wagtail/contrib/wagtailapi/api.py
--- a/wagtail/contrib/wagtailapi/api.py
+++ b/wagtail/contrib/wagtailapi/api.py
@@ -10,6 +10,7 @@
 from taggit.models import Tag
 
 from wagtail.utils.urlpatterns import decorate_urlpatterns
+from wagtail.wagtailcore.blocks import StreamValue
 
 from .endpoints import URLPath, ObjectDetailURL, PagesAPIEndpoint, ImagesAPIEndpoint, DocumentsAPIEndpoint
 from .utils import BadRequestError, get_base_url
@@ -47,6 +48,8 @@ def default(self, o):
                         return get_full_url(request, reverse(view, args=(o.pk, )))
                     else:
                         return None
+                elif isinstance(o, StreamValue):
+                    return o.stream_block.get_prep_value(o)
                 else:
                     return super(WagtailAPIJSONEncoder, self).default(o)
 


Test patch:
diff --git a/wagtail/contrib/wagtailapi/tests/test_pages.py b/wagtail/contrib/wagtailapi/tests/test_pages.py
--- a/wagtail/contrib/wagtailapi/tests/test_pages.py
+++ b/wagtail/contrib/wagtailapi/tests/test_pages.py
@@ -11,6 +11,7 @@
 from wagtail.contrib.wagtailapi import signal_handlers
 
 from wagtail.tests.demosite import models
+from wagtail.tests.testapp.models import StreamPage
 
 
 def get_total_page_count():
@@ -594,6 +595,47 @@ def test_null_foreign_key(self):
         self.assertEqual(content['feed_image'], None)
 
 
+class TestPageDetailWithStreamField(TestCase):
+    fixtures = ['test.json']
+
+    def setUp(self):
+        self.homepage = Page.objects.get(url_path='/home/')
+
+    def make_stream_page(self, body):
+        stream_page = StreamPage(
+            title='stream page',
+            slug='stream-page',
+            body=body
+        )
+        return self.homepage.add_child(instance=stream_page)
+
+    def test_can_fetch_streamfield_content(self):
+        stream_page = self.make_stream_page('[{"type": "text", "value": "foo"}]')
+
+        response_url = reverse('wagtailapi_v1:pages:detail', args=(stream_page.id, ))
+        response = self.client.get(response_url)
+
+        self.assertEqual(response.status_code, 200)
+        self.assertEqual(response['content-type'], 'application/json')
+
+        content = json.loads(response.content.decode('utf-8'))
+
+        self.assertIn('id', content)
+        self.assertEqual(content['id'], stream_page.id)
+        self.assertIn('body', content)
+        self.assertEqual(content['body'], [{'type': 'text', 'value': 'foo'}])
+
+    def test_image_block(self):
+        stream_page = self.make_stream_page('[{"type": "image", "value": 1}]')
+
+        response_url = reverse('wagtailapi_v1:pages:detail', args=(stream_page.id, ))
+        response = self.client.get(response_url)
+        content = json.loads(response.content.decode('utf-8'))
+
+        # ForeignKeys in a StreamField shouldn't be translated into dictionary representation
+        self.assertEqual(content['body'], [{'type': 'image', 'value': 1}])
+
+
 @override_settings(
     WAGTAILFRONTENDCACHE={
         'varnish': {
diff --git a/wagtail/tests/testapp/migrations/0005_streampage.py b/wagtail/tests/testapp/migrations/0005_streampage.py
new file mode 100644
--- /dev/null
+++ b/wagtail/tests/testapp/migrations/0005_streampage.py
@@ -0,0 +1,29 @@
+# -*- coding: utf-8 -*-
+from __future__ import unicode_literals
+
+from django.db import models, migrations
+import wagtail.wagtailcore.fields
+import wagtail.wagtailcore.blocks
+import wagtail.wagtailimages.blocks
+
+
+class Migration(migrations.Migration):
+
+    dependencies = [
+        ('wagtailcore', '0001_squashed_0016_change_page_url_path_to_text_field'),
+        ('tests', '0004_streammodel_richtext'),
+    ]
+
+    operations = [
+        migrations.CreateModel(
+            name='StreamPage',
+            fields=[
+                ('page_ptr', models.OneToOneField(auto_created=True, to='wagtailcore.Page', serialize=False, parent_link=True, primary_key=True)),
+                ('body', wagtail.wagtailcore.fields.StreamField((('text', wagtail.wagtailcore.blocks.CharBlock()), ('rich_text', wagtail.wagtailcore.blocks.RichTextBlock()), ('image', wagtail.wagtailimages.blocks.ImageChooserBlock())))),
+            ],
+            options={
+                'abstract': False,
+            },
+            bases=('wagtailcore.page',),
+        ),
+    ]
diff --git a/wagtail/tests/testapp/models.py b/wagtail/tests/testapp/models.py
--- a/wagtail/tests/testapp/models.py
+++ b/wagtail/tests/testapp/models.py
@@ -410,3 +410,13 @@ class StreamModel(models.Model):
         ('rich_text', RichTextBlock()),
         ('image', ImageChooserBlock()),
     ])
+
+
+class StreamPage(Page):
+    body = StreamField([
+        ('text', CharBlock()),
+        ('rich_text', RichTextBlock()),
+        ('image', ImageChooserBlock()),
+    ])
+
+    api_fields = ('body',)