One object only in GET method in Django REST Framework - python

I have a list of all my objects when I use get method by api/movies in my api, and this is ok. I want also to get only one, specyfic object when use get method by api/movies/1 but now I still have a list of all my objects... What to change in my MoviesView or in urls?
My views.py:
class MoviesView(APIView):
def get(self, request):
movies = Movie.objects.all()
serializer = MovieSerializer(movies, many=True)
return Response(serializer.data)
My appurls.py:
urlpatterns = [
url('movies', MoviesView.as_view(), name="MoviesView"),
]
And my project urls.py:
urlpatterns = [
path('admin/', admin.site.urls),
path('api/', include("api.urls")),
]
When I use routers everythig crushes... Could you help me?

You can simply use viewsets.ModelViewSet that natively implements list and retrieve.
You declare something like router.register('movies', my_views.MoviesViewSet) in you urls.py and
class MoviesViewSet(viewsets.ModelViewSet):
queryset = Movie.objects.all()
serializer_class = MovieSerializer
permission_classes = [IsAuthenticated, ]
def get_queryset(self):
return self.queryset
def get_object(self):
movie_id = self.kwargs['pk']
return self.get_queryset().filter(id=movie_id)
def retrieve(self, request, *args, **kwargs):
try:
instance = self.get_object()
except (Movie.DoesNotExist, KeyError):
return Response({"error": "Requested Movie does not exist"}, status=status.HTTP_404_NOT_FOUND)
serializer = self.get_serializer(instance)
return Response(serializer.data)
def list(self, request, *args, **kwargs):
queryset = self.get_queryset()
serializer = self.get_serializer(queryset, many=True)
return Response(serializer.data)
This approach implies that you declare a Serializer, just like:
class MovieSerializer(serializers.ModelSerializer):
class Meta:
model = Movie
fields = '__all__'
Django simply maps HOST/movies/ to list (multiple objects) and HOST/movies/PK/ to retrieve method (one single object).
Docs:
https://www.django-rest-framework.org/api-guide/viewsets/#modelviewset
https://www.django-rest-framework.org/api-guide/serializers/#modelserializer
Hope it helps.
BR.
Eduardo

I would suggest you if you want to retrieve just 1 element to use a Generic View, i.e RetrieveAPIView
It would give you all you need for getting 1 element.
from rest_framework import generics
class MoviesView(generics.RetrieveAPIView):
queryset = Movie.objects.all()
serializer_class = MovieSerializer
but you need also to change urls.py
url(r'movies/(?P<pk>[0-9]+)/$', MoviesView.as_view(), name="MoviesView"),

When you make a GET request to "api/movies/1", the url is matched to the "api/movies" path (read more in the docs), and the MoviesView's get method is called. And your get() implementation just fetches all the movies (movies = Movie.objects.all()), serializes and returns them - that's why you get the entire list.
If you want to retrieve one specific object, you need to somehow specify which object you have in mind, using its primary key (in your case, id).
1. You have to define a separate path: movies/<int:pk>/ (btw, which Django version are you using? url has been deprecated, use path instead!)
2. You have to define a detail view to handle this new case, and pass it to the path function as the second argument.
This general problem can really be solved in many ways, and depending on your app you may want to use a ViewSet instead of views. Then you don't have to define paths (urls) separately - you can use a router. You can't use routers with your view, because router needs a viewset class as its argument.
If you provide more details, I could try to suggest something more specific.

My appurls.py:
use path method
urlpatterns = [
path('movies', MoviesView.as_view(), name="MoviesView"),]
Maybe it works

Start by adding a format keyword argument to both of the views, like so
def snippet_list(request, format=None):
and
def snippet_detail(request, pk, format=None):
Now update the snippets/urls.py file slightly, to append a set of format_suffix_patterns in addition to the existing URLs
from django.urls import path
from rest_framework.urlpatterns import format_suffix_patterns
from snippets import views
urlpatterns = [
path('snippets/', views.snippet_list),
path('snippets/<int:pk>', views.snippet_detail),
]
urlpatterns = format_suffix_patterns(urlpatterns)

Related

How to list all objs and retrieve single obj using django drf generic views

I am trying to create a single class based view for retrieving, listing, and creating all of my orders. I have gotten create and retrieve to work but listing is giving some problems. I am using DRF generic views to extend my view and have added the generics.ListApiView to my class. Once I added this however my retrieve route started acting unusual. It is returning to me a list of all the orders when I just want a specific one.
I tried to just add the generics.ListApiView to my class and override the list and get_queryset functions but that just started to affect my retrieve view.
class Order(generics.ListAPIView, generics.CreateAPIView, generics.RetrieveAPIView):
permission_classes = (permissions.IsAuthenticated,)
serializer_class = addOrderSerializer
def get_queryset(self, **kwargs):
user = self.request.user
return Order.objects.filter(user=user)
def get_object(self):
pk = self.kwargs['pk']
obj = Order.objects.get(pk=pk)
return obj
def get_item_serializer(self, *args, **kwargs):
return addItemSerializer(*args, **kwargs)
def get_shipping_serializer(self, *args, **kwargs):
return addShippingSerializer(*args, **kwargs)
def create(self, request, *args, **kwargs):
user = request.user
data = request.data
orderItems = data.get('orderItems')
print(data)
if not bool(orderItems):
return Response('No Order Items', status=status.HTTP_400_BAD_REQUEST)
else:
# TODO - Create Order
orderSerializer = self.get_serializer(data=data)
orderSerializer.is_valid(raise_exception=True)
order = orderSerializer.save(user=user)
# TODO - Create Shipping Address
shippingSerializer = self.get_shipping_serializer(data=data)
shippingSerializer.is_valid(raise_exception=True)
shippingSerializer.save(order=order)
# TODO - Create Order Items and Set Order <> OrderItem Relationship
for item in orderItems:
product = Product.objects.get(pk=item['product'])
itemSerializer = self.get_item_serializer(data=item)
itemSerializer.is_valid(raise_exception=True)
item = itemSerializer.save(order=order, product=product)
# # TODO - Update Product CountInStock
product.countInStock -= item.qty
product.save()
return Response(data=orderSerializer.data)
def retrieve(self, request, *args, **kwargs):
instance = self.get_object()
s = self.get_serializer(instance=instance)
return Response(data=s.data)
def list(self, request, *args, **kwargs):
qs = self.get_queryset()
s = self.get_serializer(qs, many=True)
return Response(s.data)
from django.contrib import admin
from django.urls import path, include
from .. import views
urlpatterns = [
path('', views.Order.as_view()),
path('add/', views.Order.as_view()),
path('<int:pk>/', views.Order.as_view({'get':'retrieve'})),
]
So in conclusion the list functionality of my view is working now but it has messed up my retrieve functionality. So that the retrieve function is only returning a list even tho I am adding the pk in my url.
Can you please post your urls.py as well for better clarity ?
Currently based on your question, the issue I see is mentioned below:
You are calling class "Order" for retrieve method with some url pattern eg path/int:pk -- Which is a GET request
You are also calling same class "Order" for list method with some url pattern eg path/ -- Which is a Get request
The issue is that both Retrieve and List Generic api has a GET method: snippet added :
Conclusion:
This is an example of Method Resolution Order in python inheritance.
Therefore, even though you are trying to invoke a GET method for retrieve it is envoking the GET method of LIST api because in your class definition
class Order(generics.ListAPIView, generics.CreateAPIView, generics.RetrieveAPIView) you have inherited ListAPIView first.
Solution :
You should separate out the classes eg: ListCreateAPIView, RetrieveUpdateDestroyAPIView
Alternatively :
You can also follow below stackoverflow answer to route GET request to specific method in same class :
Multiple get methods in same class in Django views
[EDIT]
The above suggested stackoverflow answer seems to be incorrect.
For multiple GET or POST request within same class, you can use Django Viewset and routers.
I found the below link to be well explained with examples:
https://testdriven.io/blog/drf-views-part-3/

Implement REST API with Django

This is my Django model:
class M(models.Model):
a = models.IntegerField()
b = models.IntegerField()
This is the serializer:
class MSerializer(ModelSerializer):
class Meta:
model = M
fields = ['a', 'b']
I would like to be able to implement these REST APIs:
127.0.0.1:8000/m/ (GET list of all elements, POST new element)
127.0.0.1:8000/m/:id/ (GET details of element with id id)
127.0.0.1:8000/n/:a/m/ (GET all elements with a specific a field)
So far this is the view and urls that I implemented:
class MViewSet(ModelViewSet):
queryset = M.objects.all()
serializer_class = MSerializer
router = DefaultRouter()
router.register(r'm', MViewSet)
urlpatterns = [
path('', include(router.urls)),
]
However, in this way the third use case is not working. How can I modify my code to make the third case work? I'd prefer to use as few lines of code as possible (i.e., I would like to use some Django built-in functionalities).
Since it look like you want the 3rd endpoint on another root (possibly another app name n), I'll implement it is a standalone API view, and not as an action on a viewset (although both options are possible)
class FilteredMListView(ListAPIView):
serializer_class = MSerializer
def get_queryset(self):
return M.objects.filter(a=self.kwargs["a"])
Then you register it to the router using:
urlpatterns = [
path("n/<str:a>/m/", FilteredMListView.as_view())
]
For your 3rd case, I would use a ListAPIView, overriding the get_queryset method to filter by the passed value for a. The idea is that when get_queryset method is invoked, and with as many other filters you'd like to implement, the condition for a is always present. Since the value for a will be in the url, it is mandatory, and you always have in in the view's kwargs. Would look like this:
urls.py
router = DefaultRouter()
router.register(r'm', MViewSet)
urlpatterns = [
path('', include(router.urls)),
path('<a>/m', AValuesListApiView.as_view()
]
views.py
class AValuesListApiView(generics.ListAPIView):
queryset = M.objects.all()
serializer_class = MSerializer
def get_queryset(self):
return super().get_queryset().filter(score=self.kwargs["score"])

Additional views to Django Rest Framework ViewSet

I've got simple DRF ViewSet for a model, located at /gen_req/
class GenerationRequestViewSet(viewsets.ModelViewSet):
queryset = GenerationRequest.objects
serializer_class = GenerationRequestSerializer
It has default POST/GET/etc. handlers. However, I want to add another one for GET as well for different url patter (/gen_req/created_list:
class GenerationRequestViewSet(viewsets.ModelViewSet):
queryset = GenerationRequest.objects
serializer_class = GenerationRequestSerializer
#action(methods=['get'])
def special_get_handler(self, request):
queryset = GenerationRequest.filter(...) # Some extra filtering here
serializer = self.get_serializer(queryset, many=True)
return Response(serializer.data)
Is there a way to create such view within the ViewSet or another APIView should be created?
You can do it, just add such record to your urls.py file.
path('/gen_req/created_list',
GenerationRequestViewSet.as_view({'get': 'special_get_handler'}),),
You can do that, but you need to name your method accordingly. So with code, you've posted you will be able to get this method by requesting /gen_req/special_get_handler.
Of course, it should be registered in url.py. Smth like:
api_router = DefaultRouter()
api_router.register("gen_req", GenerationRequestViewSet)

Django Rest Framework: Proper retrieve view doesn't handle request when I slightly change parameter

I have this weird problem where when I try to retrieve one object from my database I receive the message {"detail":"Not found."}. I know the object is there because I can see it in my django admin. And when I try to retrieve another object it locates it fine.
urls.py
router = routers.DefaultRouter()
router.register(r'recalls', views.Recalls)
admin.autodiscover()
from rest_framework import generics, permissions, serializers
from oauth2_provider.contrib.rest_framework import TokenHasReadWriteScope, TokenHasScope
urlpatterns = [
path('admin/', admin.site.urls),
path('api/v1/', include(router.urls)),
path('api/v1/recalls/rundate/<str:run_date>/', views.Recalls.as_view({'get': 'retrieve'}), name='retrieve_by_rundate'),
path('o/', include('oauth2_provider.urls', namespace='oauth2_provider')),,
]
views.py
class Recalls(viewsets.ModelViewSet):
'''
This view will be fore retrieving a recall for a car from the database
'''
queryset = CarFax.objects.all()
serializer_class = RecallsSerializer
permission_classes = [permissions.IsAuthenticated, TokenHasReadWriteScope]
def list(self, request, **kwargs):
queryset = GetRecalls.objects.all()
serializer = RecallsSerializer(queryset, many=True)
print('LIST')
return Response(serializer.data)
def retrieve(self, request, pk=None, *args, **kwargs):
queryset = CarFax.objects.all()
#record = get_list_or_404(queryset, self.kwargs)
record = get_list_or_404(queryset, vin__exact=pk)
serializer = RecallsSerializer(record, many=True)
print('RETRIEVE')
return Response(serializer.data)
def retrieve_by_rundate(self, request, run_date=None):
queryset = CarFax.objects.all()
#record = get_list_or_404(queryset, self.kwargs)
record = get_list_or_404(queryset, run_date__exact=run_date)
serializer = RecallsSerializer(record, many=True)
print('RETRIEVE RUNDATE')
return Response(serializer.data)
I am using the URL 'http://127.0.0.1:8000/api/v1/recalls/(?P<pk>[\w-]+)/$/' (which is automatically created by DefaultRouter(). The issue is that when I look up with one parameter, let's say test1 so
'http://127.0.0.1:8000/api/v1/recalls/test1' it works, and in my console I can see the message "RETRIEVE" from my "retrieve" method being printed. However, when I replace that with another object I created and I know exists, it isn't located and that "RETRIEVE" message isn't printed. So that view obviously isn't even being reached, why is that?
Thanks in advance

Django POST with ModelViewSet and ModelSerializer 405

How can I make a ModelViewSet accept the POST method to create an object? When I attempt to call the endpoint I get a 405 'Method "POST" not allowed.'.
Within views.py:
class AccountViewSet(viewsets.ModelViewSet):
"""An Account ModelViewSet."""
model = Account
serializer_class = AccountSerializer
queryset = Account.objects.all().order_by('name')
Within serializers.py:
class AccountSerializer(serializers.ModelSerializer):
name = serializers.CharField(required=False)
active_until = serializers.DateTimeField()
class Meta:
model = Account
fields = [
'name',
'active_until',
]
def create(self, validated_data):
with transaction.atomic():
Account.objects.create(**validated_data)
within urls.py:
from rest_framework import routers
router = routers.SimpleRouter()
router.register(
prefix=r'v1/auth/accounts',
viewset=AccountViewSet,
base_name='accounts',
)
Do I need to create a specific #action? my attempts to do so have yet to be successful. If that is the case what would the url = reverse('app:accounts-<NAME>') be such that I can call it from tests? I haven't found a full example (urls.py, views.py, serializers.py, and tests etc).
I discovered what the issue was, I had a conflicting route. There was a higher level endpoint registered before the AccountViewSet.
router.register(
prefix=r'v1/auth',
viewset=UserViewSet,
base_name='users',
)
router.register(
prefix=r'v1/auth/accounts',
viewset=AccountViewSet,
base_name='accounts',
)
Django runs through each URL pattern, in order, and stops at the first one that matches the requested URL.. I should have been ordered this way:
router.register(
prefix=r'v1/auth/accounts',
viewset=AccountViewSet,
base_name='accounts',
)
router.register(
prefix=r'v1/auth',
viewset=UserViewSet,
base_name='users',
)
despite the fact that reverse('appname:acccounts-list') worked, the underlying URL router still thought I was calling the UserViewSet.
From the docs:
A ViewSet class is simply a type of class-based View, that does not provide any method handlers such as .get() or .post(), and instead provides actions such as .list() and .create().
And here is a list of supported actions:
def list(self, request):
pass
def create(self, request):
pass
def retrieve(self, request, pk=None):
pass
def update(self, request, pk=None):
pass
def partial_update(self, request, pk=None):
pass
def destroy(self, request, pk=None):
pass
So no post is not directly supported but create is.
So your end point would be v1/auth/accounts/create when using the a router instead v1/auth/accounts/post.
I honestly prefer using class based or function based views when working with DRF. It resembles regular django views more closely and makes more sense to me when working with them. You woul write you views and urls pretty much like regular django urls and views.

Categories