41

I want to create a many-to-many relationship where one person can be in many clubs and one club can have many persons. I added the models.py and serializers.py for the following logic but when I try to serialize it in the command prompt, I get the following error - What am I doing wrong here? I don't even have a HyperlinkedIdentityField

Traceback (most recent call last):
File "<console>", line 1, in <module>
File "C:\Users\user\corr\lib\site-packages\rest_framework\serializers.py", line 503, in data
ret = super(Serializer, self).data
File "C:\Users\user\corr\lib\site-packages\rest_framework\serializers.py", line 239, in data
self._data = self.to_representation(self.instance)
File "C:\Users\user\corr\lib\site-packages\rest_framework\serializers.py", line 472, in to_representation
ret[field.field_name] = field.to_representation(attribute)
File "C:\Users\user\corr\lib\site-packages\rest_framework\relations.py", line 320, in to_representation"the serializer." % self.__class__.__name__
AssertionError: `HyperlinkedIdentityField` requires the request in the serializer context. Add `context={'request': request}` when instantiating the serializer.

models.py

class Club(models.Model):
    club_name = models.CharField(default='',blank=False,max_length=100)

class Person(models.Model):
    person_name = models.CharField(default='',blank=False,max_length=200)
    clubs = models.ManyToManyField(Club)

serializers.py

class ClubSerializer(serializers.ModelSerializer):
    class Meta:
        model = Club
        fields = ('url','id','club_name','person')

class PersonSerializer(serializers.ModelSerializer):
    clubs = ClubSerializer()
    class Meta:
        model = Person
        fields = ('url','id','person_name','clubs')

views.py

class ClubDetail(generics.ListCreateAPIView):
serializer_class = ClubSerializer

def get_queryset(self):
     club = Clubs.objects.get(pk=self.kwargs.get('pk',None))
     persons = Person.objects.filter(club=club)
     return persons

class ClubList(generics.ListCreateAPIView):
    queryset = Club.objects.all()
    serializer_class = ClubSerializer


class PersonDetail(generics.RetrieveUpdateDestroyAPIView):
    serializer_class = PersonSerializer


def get_object(self):
    person_id = self.kwargs.get('pk',None)
    return Person.objects.get(pk=person_id) 

Inspecting the created serializer gives me this -

PersonSerializer(<Person: fd>):
url = HyperlinkedIdentityField(view_name='person-detail')
id = IntegerField(label='ID', read_only=True)
person_name = CharField(max_length=200, required=False)
clubs = ClubSerializer():
    url = HyperlinkedIdentityField(view_name='club-detail')
    id = IntegerField(label='ID', read_only=True)
    club_name = CharField(max_length=100, required=False)

but serializer.data gives me the error

Edit

I realized the error could be because of url patterns, so I added the following url patterns but I still get the error -

urlpatterns = format_suffix_patterns([
url(r'^$', views.api_root),
url(r'^clubs/$',
    views.ClubList.as_view(),
    name='club-list'),
 url(r'^clubs/(?P<pk>[0-9]+)/persons/$',
    views.ClubDetail.as_view(),
    name='club-detail'),
url(r'^person/(?P<pk>[0-9]+)/$',
    views.PersonDetail.as_view(),
    name='person-detail'),
])
Ankit Arora
  • 87
  • 10
qwertp
  • 839
  • 3
  • 11
  • 16

9 Answers9

51

You're getting this error as the HyperlinkedIdentityField expects to receive request in context of the serializer so it can build absolute URLs. As you are initializing your serializer on the command line, you don't have access to request and so receive an error.

If you need to check your serializer on the command line, you'd need to do something like this:

from rest_framework.request import Request
from rest_framework.test import APIRequestFactory

from .models import Person
from .serializers import PersonSerializer

factory = APIRequestFactory()
request = factory.get('/')


serializer_context = {
    'request': Request(request),
}

p = Person.objects.first()
s = PersonSerializer(instance=p, context=serializer_context)

print s.data

Your url field would look something like http://testserver/person/1/.

  • your answer resolved my issue but i didn't understand the problem? can you please explain it a little more? as I'm not calling it from command line – Naila Akbar May 19 '16 at 11:22
  • 2
    My answer is somewhat specific to instantiating a serializer outside of the request / response cycle (i.e. on the shell). If you're having issues other than this I'd suggest raising a new question. However to reiterate: you must have a `Request` object available in `context` in order to use `HyperlinkedIdentityField`. The above snippet creates a stub request to get around this where one is not normally available. – Ashley 'CptLemming' Wilson May 19 '16 at 16:06
  • if you using Versioning with REST framework you need to patch request `from rest_framework.versioning import URLPathVersioning request.version = 'v1' request.versioning_scheme = URLPathVersioning()` – Sergei Jun 01 '16 at 12:55
  • @Ashley'CptLemming'Wilson can't we get like http://127.0.0.1:8000/person/1/. What we need to do that? – MadhuP Sep 21 '18 at 14:22
  • 1
    probably you will get a message `DisallowedHost: Invalid HTTP_HOST header: 'testserver'. You may need to add 'testserver' to ALLOWED_HOSTS.`, in that case, just add `'testserver'` to `ALLOWED_HOSTS` in your `settings.py` file. – lmiguelvargasf Nov 21 '18 at 02:37
26

I have two solutions...

urls.py

1) If you are using a router.register, you can add the base_name:

router.register(r'users', views.UserViewSet, base_name='users')
urlpatterns = [    
    url(r'', include(router.urls)),
]

2) If you have something like this:

urlpatterns = [    
    url(r'^user/$', views.UserRequestViewSet.as_view()),
]

You have to pass the context to the serializer:

views.py

class UserRequestViewSet(APIView):            
    def get(self, request, pk=None, format=None):
        user = ...    
        serializer_context = {
            'request': request,
        }
        serializer = api_serializers.UserSerializer(user, context=serializer_context)    
        return Response(serializer.data)

Like this you can continue to use the url on your serializer: serializers.py

...
url = serializers.HyperlinkedIdentityField(view_name="user")
...
Slipstream
  • 13,455
  • 3
  • 59
  • 45
14

I came across the same problem. My approach is to remove 'url' from Meta.fields in serializer.py.

Diansheng
  • 1,081
  • 12
  • 19
4

Following Slipstream's answer, I edited my views.py introducing the context and now it works.

class UserViewSet(viewsets.ModelViewSet):

    """
    API endpoint that allows users to be viewed or edited.
    """
    queryset = User.objects.all().select_related('profile').order_by('-date_joined')
    serializer_class = UserSerializer

    @list_route(methods=['get'], url_path='username/(?P<username>\w+)')
    def getByUsername(self, request, username):
        serializer_context = {
            'request': request,
        }
        user = get_object_or_404(User, username=username)
        return Response(UserSerializer(user, context=serializer_context).data, status=status.HTTP_200_OK)
MDT
  • 1,599
  • 4
  • 18
  • 26
  • 1
    Note to others, ALL other suggestions here did not work, and it was only this that did. And I am confused as to why as I don't use HyperlinkedIdentityField in any part of my project... – James Gardiner Jun 05 '18 at 02:41
  • @JamesGardiner it's working just need to add `context={'request':request}` where your serializer is instantiated as **Django** suggest – Azhar Uddin Sheikh Feb 05 '22 at 14:19
3

You can simply pass None to 'request' key in context in situations where you just need the relative URL, e.g; testing a serializer in command line.

serializer = YourModelSerializer(modelInstance_or_obj, context={'request': None})
Pooya Kamranjam
  • 355
  • 3
  • 9
1

Following MDT's response, I use django-rest-framework, and solve it by changing request to request._request.

serializer_context = {'request': Request(request._request)}
1

You may simply solve it by changing the instantiation (in views.py) to thing like this:

your_serializer = YourModelSerializer(YourQuerySet_or_object, many=True,context={'request':request})
Mahrez BenHamad
  • 1,791
  • 1
  • 15
  • 21
1

For externals urls you can simply put request at None:

context={
    'request': None
},
0

In my case I had to change a field's name from url to any other thing. Hate automagic

juan Isaza
  • 3,646
  • 3
  • 31
  • 37