Django-rest-framework: Allow mocking of Request objects

Created on 12 Jan 2016  路  11Comments  路  Source: encode/django-rest-framework

I posted about this on the mailing list and in IRC. I've been trying to serialize a model that has a few HyperLinkRelated fields, and I get the impression it's very hard.

The drf documentation on serializers has a section that says you can simply do:

from rest_framework.renderers import JSONRenderer
serializer = CommentSerializer(comment)
json = JSONRenderer().render(serializer.data) 

Unfortunately, this doesn't work for HyperLinked relationships. When you try to do it with them, you get something like:

AssertionError: HyperlinkedIdentityField requires the request in the serializer context. Add context={'request': request} when instantiating the serializer.

So, I figured out I can add context attribute, like:

r = Request(request=HttpRequest())
context = dict(request=r)
serializer = CommentSerializer(comment, context=context)
json = JSONRenderer().render(serializer.data)

Which then returns the error:

django.core.exceptions.ImproperlyConfigured: Could not resolve URL for hyperlinked relationship using view name "opinioncluster-detail". You may have failed to include the related model in your API, or incorrectly configured the lookup_field attribute on this field.

On IRC, somebody suggests a crazy fix involving patching the reverse method of the serializer:

in the __init__ of my serializer, I called super() then looped through the Hyperlinked fields and set fieldobj.reverse = patched_reverse where patched_reverse wraps rest_framework.reverse but sets "urls" module as the urlconf kwarg

I could do that, but I'd love if there was a better solution. I think this bug request is basically:

  • Is there a way to make this work?
  • If so, can we document it?
  • If not, can we implement such a way?

Thanks,

Mike

All 11 comments

I finally figured this out. For some reason you must manually set the version and the versioning_scheme:

r = RequestFactory().request()
r.version = 'v3'
r.versioning_scheme = URLPathVersioning()
context = dict(request=r)
renderer = JSONRenderer()
json_str = renderer.render(
    serializer(item, context=context).data,
    accepted_media_type='application/json; indent=2',
)

This seems to work, but the HyperLinkRelated values in the JSON that is serialized have the server set to testserver. I could get around that by setting:

r.META['SERVER_NAME']

But I also need to set r.scheme to https, which doesn't seem to be possible (you get an error that r.scheme cannot be set).

I don't know if this is now a documentation issue or a bug. Either would be helpful for future people.

This _is_ an awkward thing to do yes. Hyperlinked relationships _plus_ versioning in the URL doesn't work terribly well if you want to mock up requests and use them outside of a normal view context.

(Eg. the versioning scheme only gets applied to a request at the point of a view)

The best way for us to resolve this would be to make it easier to mock Request objects. (That could _optionally_ include a view argument so that the versioning scheme / accepted media type can be determined on the request object)

I've retitled this issue accordingly.

That sounds like a good strategy. What made sense to me at one point was also to use the path attribute on the request to get the version, just like, I presume, a normal web request would.

What made sense to me at one point was also to use the path attribute on the request to get the version, just like, I presume, a normal web request would.

Indeed yes - the mocked request would need to include an appropriate path if path based versioning is being used, in order for serializers to correctly reverse the resulting URLs.

And apologies for your pain on this - you hit an awkward corner there! :-/

Some notes to other desperate sweepers of awkward corners:

  • The versioning_scheme did not work for me, but it might help, if you add version namespace into definition of the field, like serializers.HyperlinkedIdentityField(view_name="v1:downloads-detail")
  • If you want to specify the host, you can do by HTTP_HOST parameter to the request()
  • For me works a bit simpler request mocking code:
        from django.test import RequestFactory

        request = RequestFactory().request(HTTP_HOST='localhost:8001')
        serializer = AssetSerializer(context={'request': request})
        obj_json = serializer.to_representation(obj)
  • With django.site framework, you can get the http_host like:
        from django.contrib.sites.models import Site
        http_host = Site.objects.get_current().domain

Note to self - work from #5609 might be relevant.

@rpkilby now https://github.com/encode/django-rest-framework/pull/5609 is merged does that move this along?

I'm trying to follow this to render data via a class that implements ListModelMixin and this seems even more tricky, I can't work out how to pass my queryset to the .list() method and get something out the other side.

Looking at the docs I realised i needed to call .as_view() to get realise the view and be able to call .as_list()

import sys

from display_xml import XML

from workbaskets.views import *

items_list = ItemsViewSet.as_view({'get': 'list'})
response = items_list(request, workbaskets, format='xml')

print(response.rendered_content)
Was this page helpful?
0 / 5 - 0 ratings