AssertionError: `HyperlinkedIdentityField` requires the request in the serializer context - python

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'),
])

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/.

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")
...

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

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)

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})

Following MDT's response, I use django-rest-framework, and solve it by changing request to request._request.
serializer_context = {'request': Request(request._request)}

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})

For externals urls you can simply put request at None:
context={
'request': None
},

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

Related

Why is not `url` showing up in the DRF Response

I can't get url to be returned when I use it in a HyperlinkedModelSerializer.
# models.py
class Promotion(TimeStampedMixin, models.Model):
name = models.CharField(max_length=300)
# ...
# views.py
class PromotionViewSet(viewsets.ModelViewSet):
serializer_class = PromotionSerializer
queryset = Promotion.objects.all()
def retrieve(self, request, *args, **kwargs):
instance = self.get_object()
serializer = self.get_serializer(instance, context={'request': request})
return Response(serializer.data)
# serializers.py
class PromotionSerializer(serializers.HyperlinkedModelSerializer):
url = serializers.HyperlinkedRelatedField(
view_name="campaigns:promotion-detail", read_only=True
)
class Meta:
model = Promotion
fields = (
"url",
"id",
"name",
)
The JSON output I receive when querying curl -X GET http://localhost/api/promotion/2/:
{"id":2,"name":"My promotion"}
If I use reverse to check if the view_name in the HyperlinkedRelatedField exists, it prints the correct URL.
My question is: why doesn't the url show up in the response? It works any all my other views (comparing code with classes that works hasn't helped). Read the DRF documentation but I see nothing new (using version 3.11.0)
To get the url representation to the current object you should use the HyperlinkedIdentityField
class PromotionSerializer(serializers.HyperlinkedModelSerializer):
url = serializers.HyperlinkedIdentityField(
view_name="campaigns:promotion-detail", read_only=True
)
class Meta:
model = Promotion
fields = (
"url",
"id",
"name",
)

How to Query model filed elements in the same URL

I am writing a single model application in DRF. My model looks like this:
class Superhero(models.Model):
squad_name = models.CharField(max_length=100)
hometown = models.CharField(max_length=30)
formed = models.DateField()
active = models.BooleanField()
members = JSONField()
My viewset looks like this:
class SuperheroViewSet(viewsets.ViewSet):
"""
A simple ViewSet for listing or retrieving superheros.
"""
serializer_class = SuperheroSerializer
def list(self, request):
"""list superhero object"""
queryset = Superhero.objects.filter()
serializer = SuperheroSerializer(queryset, many=True)
return Response(serializer.data)
def retrieve(self, request, pk=None):
queryset = Superhero.objects.filter()
superhero = get_object_or_404(queryset, pk=pk)
serializer = SuperheroSerializer(superhero)
return Response(serializer.data)
and finally, my router is:
router = DefaultRouter()
router.register(r'superhero', SuperheroViewSet, basename='superhero')
urlpatterns = router.urls
Now how do I set a URL,so I would query the members field like:
//superhero/{id}/members to get specific id members. I tried drf nested URL but didn't work. The url I have works for superhero/ and superhero/{id}.
You should use detailed viewset action.
Your code would looks smth like this:
from rest_framework.decorators import action
from rest_framework.shortcuts import get_object_or_404
from rest_framework.response import Response
class SuperheroViewSet():
...
#action(detail=True, methods=['get'], url_path='members')
def get_superhero_members(self, request, pk=None):
superhero = get_object_or_404(self.get_queryset(), pk=pk)
members = <get members of your hero>
return Response(members)
You should also probably use custom serializer for members and in response return: return Response(CustomSerializer(members).data)

How do you create object with modelviewset and POST request?

Im trying to add a comment to my db, but getting error 'OrderedDict' object has no attribute 'pk'
The part of react.js code handling the POST request:
addComment() {
let url = this.props.post_url
axios.post('/api/comments/', {
post: url,
user: "http://127.0.0.1:8000/api/users/1/?format=json",
text: document.getElementsByName(url)[0].value,
csrfmiddlewaretoken: document.getElementsByName("csrfmiddlewaretoken")[0].value},
)
.then(function (response) {
console.log(response);
})
.catch(function (error) {
console.log(error);
});
}
My serializers.py:
from django.contrib.auth.models import User
from rest_framework import serializers
from .models import Post, Comment
class UserSerializer(serializers.HyperlinkedModelSerializer):
class Meta:
model = User
fields = ('id', 'username', 'url')
class CommentSerializer(serializers.HyperlinkedModelSerializer):
#user = UserSerializer(many=False, required=False)
class Meta:
model = Comment
fields = ('id', "post", "user", 'text')
read_only_fields = ('id', "user")
def create(self):
user = None
request = self.context.get("request")
if request and hasattr(request, "user"):
user = request.user
class PostSerializer(serializers.HyperlinkedModelSerializer):
#user = UserSerializer(required=False)
comments = CommentSerializer(many=True, required=False, read_only=True)
class Meta:
model = Post
fields = ('id', 'title', "user", "url", "comments", 'text')
read_only_fields = ('id', "url", "comments")
def save(self):
user = None
request = self.context.get("request")
if request and hasattr(request, "user"):
user = request.user
My views.py
from django.contrib.auth.models import User
from api.serializers import UserSerializer
from rest_framework import viewsets
from .models import Comment, Post
from .serializers import CommentSerializer, PostSerializer
class UserViewSet(viewsets.ModelViewSet):
queryset = User.objects.all()
serializer_class = UserSerializer
class CommentViewSet(viewsets.ModelViewSet):
queryset = Comment.objects.all()
serializer_class = CommentSerializer
class PostViewSet(viewsets.ModelViewSet):
queryset = Post.objects.all()
serializer_class = PostSerializer
When sending the Post-request it goes througth normaly. If I remove one of the fields it returnes a 400. Now Im getting this 500 [Internal server] error.
AttributeError: 'OrderedDict' object has no attribute 'pk'
The error seems to be comming from:
/home/halvor1606/.virtualenvs/django-react/local/lib/python2.7/site-packages/rest_framework/relations.py in get_url
# Unsaved objects will not yet have a valid URL.
if hasattr(obj, 'pk') and obj.pk in (None, ''):
return None
Here-> lookup_value = getattr(obj, self.lookup_field) ...
kwargs = {self.lookup_url_kwarg: lookup_value}
return self.reverse(view_name, kwargs=kwargs, request=request, format=format)
def get_name(self, obj):
return six.text_type(obj)
▶ Local vars are as follows:-
Variable Value
request <rest_framework.request.Request object at 0x7f4e59e75b90>
view_name 'post-detail'
obj OrderedDict([(u'title', u'adskjfj|'), (u'user', <User: halvor1606>), (u'text', u'kjkldsjf')])
self HyperlinkedIdentityField(read_only=True, view_name='post-detail')
format None
Read the other Questions with the same error. Didn't find one that solved my problem.
Thank you!
Edit:
Solved it by adding this to my post serializer:
def create(self, validated_data):
tmp_post = validated_data
user = None
request = self.context.get("request")
if request and hasattr(request, "user"):
user = request.user
post = Post.objects.create(
user=user,
title=tmp_post['title'],
text=tmp_post['text'],
)
return post
I think this is simply because your url here is empty:
addComment() {
let url = this.props.post_url
axios.post('/api/comments/', {
post: url,
user: "http://127.0.0.1:8000/api/users/1/?format=json",
text: document.getElementsByName(url)[0].value,
csrfmiddlewaretoken: document.getElementsByName("csrfmiddlewaretoken")[0].value},
)
.then(function (response) {
console.log(response);
})
.catch(function (error) {
console.log(error);
});
}
Since you specified class CommentSerializer(serializers.HyperlinkedModelSerializer):, this was mentioned in the document:
There needs to be a way of determining which views should be used for
hyperlinking to model instances.
By default hyperlinks are expected to correspond to a view name that
matches the style '{model_name}-detail', and looks up the instance by
a pk keyword argument.
So the HyperlinkedModelSerializer tries to find a view which should be used for the linking to Post object and could not find it. Highly suspected that the post url is with empty id

Django rest framework - hyperlinking non-model serializer fields

I'm struggling to figure out how to implement the Hyperlinked relationships for non-model querysets. I have a viewset:
class GGGViewSet(viewsets.ViewSet):
def list(self, request):
serializer_class = manufacture_serializer(ar)
serializer = serializer_class(
instance = sample.values(), many=True
)
return Response(serializer.data)
def retrieve(self, request, pk=None):
try:
anobject = sample[pk]
except KeyError:
return Response(status=status.HTTP_404_NOT_FOUND)
except ValueError:
return Response(status=status.HTTP_400_BAD_REQUEST)
serializer_class = manufacture_serializer(ar)
serializer = serializer_class(instance=anobject)
return Response(serializer.data)
I am trying to get the value resource at /data/trait/ to be rendered as a link, where:
trait-list
data/trait/
{
"value": 12334,
"another_value": 45672,
}
trait-detail
data/trait/value/
{
"value":12334
}
Attempted:
url = serializers.HyperlinkedIdentityField(view_name='trait-list')
Error: AttributeError at /asvo/data/trait/ 'AObj' object has no attribute 'pk'.
Any ideas on the best way to approach this would be appreciated. :)
You were probably quite close. Based on the information provided, here's something that demonstrates HyperlinkedIdentityField usage without relying on an actual Django model. I had to use my imagination when filling in the details of your architecture.
from rest_framework import routers
from rest_framework import serializers
from rest_framework import status
from rest_framework import viewsets
from rest_framework.response import Response
# This goes in the URL routing file
router = routers.DefaultRouter()
router.register(r'trait', GGGViewSet, base_name='trait')
urlpatterns = router.urls
# The "model"
class Thing(object):
def __init__(self, pk, value, another_value):
self.pk = pk
self.value = value
self.another_value = another_value
# The "queryset"
sample = {
'1': Thing(1, 12334, 45672),
'2': Thing(2, 12335, 45673),
'3': Thing(3, 12336, 45674)
}
# The serializer
class manufacture_serializer(serializers.Serializer):
pk = serializers.HyperlinkedIdentityField(
view_name='trait-detail', read_only=True)
value = serializers.IntegerField()
another_value = serializers.IntegerField()
class Meta:
fields = ['pk', 'value', 'another_value']
# The view
class GGGViewSet(viewsets.ViewSet):
def list(self, request):
serializer = manufacture_serializer(
instance=sample.values(), many=True, context={'request': request})
return Response(serializer.data)
def retrieve(self, request, pk=None):
try:
anobject = sample[pk]
except KeyError:
return Response(status=status.HTTP_404_NOT_FOUND)
except ValueError:
return Response(status=status.HTTP_400_BAD_REQUEST)
serializer = manufacture_serializer(
instance=anobject, context={'request': request})
return Response(serializer.data)
I did not fully understand the second half of the question regarding the data/trait/ and data/trait/value/, but hopefully this is enough to further you along.
Cheers!

Django-Rest-Framework Relationships & Hyperlinked API issues

I am having a go at the django-rest-framework. It was all going fine until I got to the Relationships & Hyperlinked API part of the tutorial. The error I am getting now after messing with it for a bit is:
ImproperlyConfigured at /api/users/ "^\.(?P<format>[a-z0-9]+)\.(?P<format>[a-z0-9]+)$" is not a valid regular expression: redefinition of group name u'format' as group2; was group 1
I tried doing some research into this but can't seem to find anything and more I mess with it the more that goes wrong
Heres my code:
modules.py
class Home(models.Model):
user = models.ForeignKey(User)
#address ect
serializers.py
class UserSerializer(serializers.HyperlinkedModelSerializer):
username = serializers.HyperlinkedRelatedField(many=True, read_only=True, view_name='home-detail')
class Meta:
model = User
fields = ('url', 'username', 'home')
class HomeSerializer(serializers.HyperlinkedModelSerializer):
owner = serializers.Field(source='owner.username')
highlight = serializers.HyperlinkedIdentityField(view_name='home-highlight', read_only=True, format='html')
class Meta:
model = Home
fields = ('url', 'owner', 'postcode')
api.py
#api_view(('GET',))
def api_root(request, format=None):
return Response({
'users': reverse('user-list', request=request, format=format),
'homes': reverse('home-list', request=request, format=format)
})
class UserList(generics.ListCreateAPIView):
queryset = User.objects.all()
serializer_class = UserSerializer
class UserDetail(generics.RetrieveUpdateDestroyAPIView):
queryset = User.objects.all()
serializer_class = UserSerializer
class HomeList(generics.ListCreateAPIView):
queryset = Home.objects.all()
serializer_class = HomeSerializer
class HomeDetail(generics.RetrieveUpdateDestroyAPIView):
queryset = Home.objects.all()
serializer_class = HomeSerializer
class HomeHighlight(generics.GenericAPIView):
queryset = Home.objects.all()
renderer_classes = (renderers.StaticHTMLRenderer,)
def get(self, request, *args, **kwargs):
snippet = self.get_object()
return Response(snippet.highlighted)
urls.py
urlpatterns = format_suffix_patterns([
url(r'^$', api.api_root),
url(r'^users/$',
api.UserList.as_view(),
name='user-list'),
url(r'^users/(?P<pk>[0-9]+)/$',
api.UserDetail.as_view(),
name='user-detail'),
url(r'^home/$',
api.HomeList.as_view(),
name='home-list'),
url(r'^home/(?P<pk>[0-9]+)/$',
api.HomeDetail.as_view(),
name='home-detail'),
url(r'^home/(?P<pk>[0-9]+)/highlight/$',
api.HomeHighlight.as_view(),
name='home-highlight')
])
urlpatterns += [
url(r'^api-auth/', include('rest_framework.urls',
namespace='rest_framework')),
]
urlpatterns = format_suffix_patterns(urlpatterns)
You are calling format_suffix_patterns twice, so Django has no idea how to parse the URL because there are two format groups.
You shouldn't need the first call, as the second call takes care of it for you (and allows for TokenAuthentication to still have the suffixes).
Atleast change router.DefaultRouter() to router.SimpleRouter() in urls.py file

Categories