Django REST framework foreign key with generics.ListCreateAPIView - python

How it is possible to get foreign key assigned in url with Django REST Framework?
class CommentList(generics.ListCreateAPIView):
serializer_class = CommentSerializer
pagination_class = StandardResultsSetPagination
queryset = Comment.objects.all()
def get(self, *args, **kwargs):
serializer = CommentSerializer(comment, many=True)
return super(CommentList, self).get(*args, **kwargs)
My goal is to use this URL (urls.py):
url(r'^event/(?P<pk>[0-9]+)/comments', views.CommentList.as_view())
Somehow I managed to get foreign key with this way
class CommentLikeList(APIView):
def get(self, request, *args, **kwargs):
key = self.kwargs['pk']
commentLikes = CommentLike.objects.filter(pk=key)
serializer = CommentLikeSerializer(commentLikes, many=True)
return Response(serializer.data)
def post(self):
pass
But I don't know how to get foreign key with such URL using
''generics.ListCreateAPIView''
http://127.0.0.1:8000/event/<eventnumber>/comments

If you want to get the pk. You can use lookup_url_kwarg attribute from ListCreateAPIView class.
class CommentLikeList(ListCreateAPIView):
def get(self, request, *args, **kwargs):
key = self.kwargs[self.lookup_url_kwarg]
commentLikes = CommentLike.objects.filter(pk=key)
serializer = CommentLikeSerializer(commentLikes, many=True)
return Response(serializer.data)
lookup_url_kwarg - The URL keyword argument that should be used for
object lookup. The URL conf should include a keyword argument
corresponding to this value. If unset this defaults to using the same
value as lookup_field.
The default value for lookup_field attribute is 'pk'. So, if you change your url keyword argumento from another different to pk, you should define lookup_url_kwarg then.
class CommentLikeList(ListCreateAPIView):
lookup_url_kwarg = 'eventnumber'
You can inspect all DRF classes methods and attributes over here:
http://www.cdrf.co/

Related

Django ViewSet serializer_class is being ignored

I have two models: ModelA and ModelB, with their corresponding serializers ModelASerializer and ModelBSerializer
In a specific viewset, called MyViewSet i have the follwing structure:
class MyViewSetRoot(viewsets.ModelViewSet):
http_method_names = ["get"]
# The returned values are of type "ModelA", so I need it to use that serializer
serializer_class = ModelASerializer
queryset = ""
Finally, in my actual view, I do something like this:
class MyViewSet(MyViewSetRoot):
get(self, request: HttpRequest, *args, **kwargs) -> Response:
ModelA_queryset = ModelA.objects.all()
return Response(
data=ModelA_queryset,
status=status.HTTP_200_OK,
)
I would expect in that case for the queryset to be serialized using the ModelASerializer that I specified in the serializer_class field. However, I get the error
Object of type ModelA is not JSON serializable
If I do this instead:
class MyViewSet(MyViewSetRoot):
get(self, request: HttpRequest, *args, **kwargs) -> Response:
ModelA_queryset = ModelA.objects.all()
serialized_queryset = ModelASerializer(ModelA_queryset, many=True)
return Response(
data=serialized_queryset.data,
status=status.HTTP_200_OK,
)
It works just fine, but I want to avoid serializing explicitly in the view.
Any ideas on what could be actually going on? Am I forced to serialize explicitly in this case?
I think you don't need to customize the get function. In ModelViewSet, the function for the GET API, is list or retrieve. But you don't need to redefine it.
class MyViewSetRoot(viewsets.ModelViewSet):
http_method_names = ["get"]
serializer_class = ModelASerializer
queryset = ModelA.objects.all()
class MyViewSet(MyViewSetRoot):
pass

Cant create a record within viewset custom view based on url parameters

Hello I have a django rest framework view set. For the create view I want to create a custom view that will create a new record based on two different parameters that are passed on through the url which are namespace and path. I looked at the documentation but i couldnt find how it should look like. I am noit sure what I need to do in order to create a record based on both url parameters.
I basically tried setting the create to a CreateAPIView but it did not work
class PreferenceViewSet(viewsets.ViewSet):
queryset = Preference.objects.all()
serializer_class = PreferenceSerializer
def get_permissions(self):
if self.action == 'create' or self.action == 'destroy':
permission_classes = [IsAuthenticated]
else:
permission_classes = [IsAdminUser]
return [permission() for permission in permission_classes]
def list(self, request):
queryset = Preference.objects.all()
serializer = PreferenceSerializer(queryset, many=True)
return Response(serializer.data)
def create(self, request):
queryset = Preference.objects.all()
serializer = PreferenceSerializer(queryset, many=True)
return Response(serializer.data)
I want to setup the create to create a preference with the two parameters that are passe in the url
path('preferences/<str:namespace>/<str:path>', preference_path, name='preference-path'),
I wanted it to create a new object with the namespace and path
You need to do this in 2 steps:
Add the url arguments to serializer context from viewset
Override create method on the serializer and use data passed on the context to create the record
So, at first override get_serializer_context method to add the arguments to context:
class PreferenceViewSet(viewsets.ViewSet):
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self._namespace = None
self._path = None
...
...
def get_serializer_context(self):
context = super().get_serializer_context()
context.update(namespace=self._namespace, path=self._path)
return context
def create(self, request):
self._namespace = self.kwargs['namespace']
self._path = self.kwargs['path']
queryset = Preference.objects.all()
serializer = PreferenceSerializer(queryset, many=True)
return Response(serializer.data)
Now, you can access the parameters inside the overriden create method of the serializer and create the record as you want e.g.:
class PreferenceSerializer(serializers.HyperlinkedModelSerializer):
...
...
def create(self, validated_data):
namespace = self.context['namespace']
path = self.context['path']
# Create object here based on the params

Modifying a Django Rest Framework Request

I have a Django rest framework api set up, and I'm trying to insert the current time into incoming PUT requests. I currently have:
class ItemViewSet(viewsets.ModelViewSet):
queryset = Item.objects.filter(done = False).order_by('-time')
serializer_class = ItemSerializer
paginate_by = None
def list(self, request, *args, **kwargs):
self.object_list = self.filter_queryset(self.get_queryset())
serializer = self.get_serializer(self.object_list, many=True)
return Response({'results': serializer.data})
This handles partial updates, but I would like to be able to send a request setting an Item to done = True and have the api also insert a unix timestamp into the data sent to the serializer. Could I alter the request object like this, or is there a better way?
def put(self, request, *args, **kwargs):
request.data['time'] = time.time()
return self.partial_update(request, *args, **kwargs)
Instead of modifying request, override serializer's method update.
Class ItemlSerializer(serializers.ModelSerializer):
class Meta:
model = ItemModel
fields = '__all__'
read_only_fields = ('time',)
def update(self, instance, validated_data):
instance.time = time.time()
return super().update(instance, validated_data)
You make a Parent serializer mixin with a serializer method field. Then all your serializers can inherit this serializer mixin.
class TimeStampSerializerMixin(object):
timestamp = serializers.SerializerMethodField()
def get_timestamp((self, obj):
return str(timezone.now())

Django Rest Framework permissions not being called

I'm currently converting all my views to generics, as I like how cleaner the code gets. I am trying to make my User detail view, like so:
# User.views
from Common import view_mixins, view_filters, view_permissions
class UserDetail(view_mixins.IntOrStrLookupMixin, generics.RetrieveUpdateDestroyAPIView):
queryset = Profile.objects.all()
lookup_fields = ('user__pk', 'user__username')
lookup_url_kwarg = 'userid'
filter_backends = (view_filters.ResourceVisibilityFilter, )
permission_classes = (view_permissions.IsOwnerOrReadOnly, )
serializer_class = ProfileSerializer
def update(self, request, *args, **kwargs):
user = self.get_object()
return Response('whatever')
# Common.view_permissions
SAFE_METHODS = ('GET', 'HEAD', 'OPTIONS')
class IsOwnerOrReadOnly(permissions.BasePermission):
'''
Owner of object can GET, PUT, DELETE. Everyone else can GET.
'''
def has_permission(self, request, view):
return True
def has_object_permission(self, request, view, obj):
print('did you call me?')
return (
request.method in SAFE_METHODS
or
obj.user == request.user
)
# Common.view_mixins
class IntOrStrLookupMixin(object):
"""
Apply to views that can be looked up by slug or pk
"""
def get_object(self):
queryset = self.get_queryset()
queryset = self.filter_queryset(queryset)
filter = {}
for field in self.kwargs:
argument = self.kwargs[field]
if is_int(argument):
filter[self.lookup_fields[0]] = argument
else:
filter[self.lookup_fields[1]] = argument
return get_object_or_404(queryset, **filter)
So my issue is, the permission is never called. I can get the user just fine, but anyone can do PUT or DELETE on which I am trying to prevent.
In my mixin, I had to call the check_object_permissions method.
class IntOrStrLookupMixin(object):
"""
Apply to views that can be looked up by slug or pk
"""
def get_object(self):
queryset = self.get_queryset()
queryset = self.filter_queryset(queryset)
filter = {}
for field in self.kwargs:
argument = self.kwargs[field]
if is_int(argument):
filter[self.lookup_fields[0]] = argument
else:
filter[self.lookup_fields[1]] = argument
obj = get_object_or_404(queryset, **filter)
self.check_object_permissions(self.request, obj)
return obj
I think I encountered this problem. The object level permissions are only called when you use get_object() method to get the object being operated on. Update your update() with this line and you should see the permissions being called. And update your custom get_object() to either call the super method or call the permissions directly
class UserDetail(view_mixins.IntOrStrLookupMixin, generics.RetrieveUpdateDestroyAPIView):
... blah blah blah
def update(self, request):
user = self.get_object()
return Response('whatever')
class IntOrStrLookupMixin(object):
def get_object(self):
... retrieve the object ...
self.check_object_permissions(self.request, obj)
return obj
Edit: I filed a bug report about this with the DRF team and they updated the docs. On the permission page in the docs it says "Object level permissions are run by REST framework's generic views when .get_object() is called". I agree that this is a rather subtle thing and is easy to miss.
Edit #2: Looks like the problem is not only in the update() not calling get_object(), but also in the IntOrStrLookupMixin mixin redefining get_object() method. Updated the code to reflect

Retrieving current user inside Serializer Method in Django Rest API

I have a serializer for user profiles in Django Rest:
class UserProfileSerializer(serializers.ModelSerializer):
......................
......................
status = serializers.SerializerMethodField()
def get_status(self, obj):
user = self.context['request'].user
if obj.user.userprofile in user.followed_userprofiles_set.all():
return "following"
else:
return "notfollowing"
class Meta:
model = UserProfile
fields = (...., 'status',...)
And I have two views that use this serializer:
class Followers(APIView):
def get(self, request, format=None):
#user who follow current user
users = request.user.userprofile.followers.all()
userprofiles= UserProfile.objects.filter(user__in=users)
serializer = UserProfileSerializer(userprofiles, many=True)
return Response(serializer.data)
and
class Friends(mixins.ListModelMixin, generics.GenericAPIView):
queryset = UserProfile.objects.all()
serializer_class = UserProfileSerializer
permission_classes = (permissions.IsAuthenticated,)
def get(self, request, *args, **kwargs):
return self.list(request, *args, **kwargs)
def get_queryset(self):
.................
.................
return queryset
One view is using APIView and other is using genericAPIView. When i request from genericAPIView, its working properly. But when i request from APIView, its giving me key error. How to retrieve the current user inside serializer method when APIView is used?
Since you are manually instantiating the UserProfileSerializer in your APIView class without passing the context, KeyError exception gets raised.
You should pass the request in context parameter when instantiating the UserProfileSerializer in your APIView.
class Followers(APIView):
def get(self, request, format=None):
#user who follow current user
users = request.user.userprofile.followers.all()
userprofiles= UserProfile.objects.filter(user__in=users)
context = {'request':request} # prepare serializer context
serializer = UserProfileSerializer(userprofiles, many=True, context=context) # pass context
return Response(serializer.data)

Categories

Resources