Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Wagtail: Serializing page model

I am using wagtail as a REST backend for a website. The website is built using react and fetches data via wagtails API v2.

The SPA website needs to be able to show previews of pages in wagtail. My thought was to override serve_preview on the page model and simply seralize the new page as JSON and write it to a cache which could be accessed by my frontend. But im having trouble serializing my page to json. All attempts made feel very "hackish"

I've made several attempts using extentions of wagtails built in serializers but without success:

Atempt 1:

   def serve_preview(self, request, mode_name):

        from wagtail.api.v2.endpoints import PagesAPIEndpoint

        endpoint = PagesAPIEndpoint()
        setattr(request, 'wagtailapi_router',
                WagtailAPIRouter('wagtailapi_v2'))
        endpoint.request = request
        endpoint.action = None
        endpoint.kwargs = {'slug': self.slug, 'pk': self.pk}
        endpoint.lookup_field = 'pk'

        serializer = endpoint.get_serializer(self)

Feels very ugly to use router here and set a bunch of attrs

Attempt 2:

 def serve_preview(self, request, mode_name):
    from wagtail.api.v2.endpoints import PagesAPIEndpoint

    fields = PagesAPIEndpoint.get_available_fields(self)
    if hasattr(self, 'api_fields'):
        fields.extend(self.api_fields)
    serializer_class = get_serializer_class(
        type(self), fields, meta_fields=[PagesAPIEndpoint.meta_fields], base=PageSerializer)
    serializer = serializer_class(self)

Better but i get context issues:

Traceback (most recent call last):
...
File "/usr/local/lib/python3.5/site-packages/wagtail/api/v2/serializers.py", line 92, in to_representation    
self.context['view'].seen_types[name] = page.specific_class
    KeyError: 'view'

Any toughts?

like image 382
Richard Avatar asked Mar 01 '17 18:03

Richard


2 Answers

Solved it by diving through the source code.

First define an empty dummy view:

class DummyView(GenericViewSet):

    def __init__(self, *args, **kwargs):
        super(DummyView, self).__init__(*args, **kwargs)

        # seen_types is a mapping of type name strings (format: "app_label.ModelName")
        # to model classes. When an object is serialised in the API, its model
        # is added to this mapping. This is used by the Admin API which appends a
        # summary of the used types to the response.
        self.seen_types = OrderedDict()

Then use this view and set the context of your serializer manually. Im also using the same router as in my api in my context. It has methods which are called by the PageSerializer to resolve some fields. Kinda strange it is so tightly coupled with the wagtail api but at least this works:

def serve_preview(self, request, mode_name):

        import starrepublic.api as StarApi

        fields = StarApi.PagesAPIEndpoint.get_available_fields(self)
        if hasattr(self, 'api_fields'):
            fields.extend(self.api_fields)
        serializer_class = get_serializer_class(
            type(self), fields, meta_fields=[StarApi.PagesAPIEndpoint.meta_fields], base=PageSerializer)
        serializer = serializer_class(
            self, context={'request': request, 'view': DummyView(), 'router': StarApi.api_router})

Dont forget to import:

from wagtail.api.v2.serializers import get_serializer_class
from rest_framework.viewsets import GenericViewSet
from rest_framework import status
from rest_framework.response import Response
from django.http import JsonResponse
from django.http import HttpResponse
like image 116
Richard Avatar answered Oct 29 '22 06:10

Richard


Possibly a non-answer answer, but I too have had challenges in the area of DRF, Wagtail's layering on top of DRF, and the need to cache json results (DRF has no built-in caching as far as I can tell, so that's an additional challenge). In a recent project, I ended up just building a list of dictionaries in a view and sending them back out with HttpResponse(), bypassing DRF and Wagtail API altogether. The code ended up simple, readable, and was easy to cache:

import json
from django.http import HttpResponse
from django.core.cache import cache

data = cache.get('mydata')
if not data:

    datalist = []
    for foo in bar:
        somedata = {}
        # Populate somedata, "serializing" fields manually...
        datalist.append(somedata)

# Cache for a week.
data = datalist
cache.set('mydata', datalist, 60 * 60 * 24 * 7)

return HttpResponse(json.dumps(data), content_type='application/json')

Not as elegant as using the pre-built REST framework, but sometimes the simpler approach is just more productive...

like image 36
shacker Avatar answered Oct 29 '22 08:10

shacker