django rest framework - override viewsets.ViewSet#retrieve in order to use 2 parameters for lookup resource - django-rest-framework

I'd like to use two model's attributes to lookup a Route's record
models.py
class Route(DateTimeModel):
start_poi = models.ForeignKey(Poi, related_name="start_pois", on_delete=models.CASCADE)
end_poi = models.ForeignKey(Poi, related_name="end_pois", on_delete=models.CASCADE)
...
def __str__(self):
return 'From %s to %s' % (self.start_poi.slug, self.end_poi.slug)
views.py
class RouteViewSet(viewsets.ViewSet):
http_method_names = ['get']
#staticmethod
def list(request):
queryset = ...
serializer = RouteSerializer(queryset, many=True)
return Response(serializer.data)
#staticmethod
def retrieve(request, from_poi_slug, to_poi_slug):
queryset = ...
route = get_object_or_404(queryset, from_poi_slug=from_poi_slug, to_poi_slug=to_poi_slug)
serializer = RouteSerializer(route)
return Response(serializer.data)
urls.py
urlpatterns.extend([
path(rf'{BASE_API_PATH}/routes/(?P<from_poi_slug>[-\w]+)/(?P<to_poi_slug>[-\w]+)', RouteViewSet),
])
I get
Not Found: /api/v1/routes/xyz/abc
[29/Apr/2019 10:07:01] "GET /api/v1/routes/molo-santa-maria/kennedy-ne HTTP/1.1" 404 13191
What am I missing?
How can I properly override #retrieve and correctly configure urls?

Since you are only using read operations, you could use ReadOnlyModelViewSet.
#views.py
class RouteViewSet(viewsets.ReadOnlyModelViewSet):
http_method_names = ['get']
def list(self, request, *args, **kwargs):
queryset = ...
serializer = RouteSerializer(queryset, many=True)
return Response(serializer.data)
def retrieve(self, request, *args, **kwargs):
from_poi_slug = kwargs['from_poi_slug']
to_poi_slug = kwargs['to_poi_slug']
queryset = ...
route = get_object_or_404(queryset, from_poi_slug=from_poi_slug, to_poi_slug=to_poi_slug)
serializer = RouteSerializer(route)
return Response(serializer.data)
and in your urls.py
urlpatterns.extend([
path(rf'{BASE_API_PATH}/routes/(<from_poi_slug>)/(<to_poi_slug>)/', RouteViewSet.as_view({"get": "retrieve"})),
])

Related

Django REST - PUT and PATCH create new object instead of updating

I'm creating a basic Django REST API that will work with a catalogue of products. I have to check every time a non-registered user GETS a product and add to the current value. And the GET and DELETE work perfectly fine. The problem is that whenever I want to update a product, a new Product is created instead.
I have 2 models:
class Brand(models.Model):
brand_name = models.CharField(max_length=500)
def __str__(self):
return self.brand_name
class Product(models.Model):
sku = models.PositiveIntegerField(primary_key=True, validators=[MaxValueValidator(999999999999999)])
product_name = models.CharField(max_length=500)
product_price = models.DecimalField( max_digits=50, decimal_places=2)
product_brand = models.ForeignKey(Brand, on_delete=models.CASCADE)
times_searched_anonymous = models.PositiveIntegerField(default=0)
def __str__ (self):
return self.product_name + ' (' + self.product_brand.brand_name + ')' + ' - $' + str(self.product_price)
These are my serializers:
class ProductSerializer(serializers.ModelSerializer):
class Meta:
model = Product
fields = [
"sku",
"product_name",
"product_price",
"product_brand",
"times_searched_anonymous"
]
class BrandSerializer(serializers.ModelSerializer):
class Meta:
model = Brand
fields = [
"brand_name",
"pk",
]
And this is the view I have:
class ProductDetail(generics.RetrieveUpdateDestroyAPIView):
"""
Retrieve, update or delete a product instance.
"""
permission_classes = [permissions.IsAuthenticatedOrReadOnly]
def get_object(self, pk):
try:
return Product.objects.get(pk=pk)
except Product.DoesNotExist:
raise Http404
def get(self, request, pk, format=None):
product = self.get_object(pk)
serializer = ProductSerializer(product)
if not request.user.is_authenticated:
data2 = {'times_searched_anonymous': product.times_searched_anonymous + 1}
serializer2 = ProductSerializer(product, data=data2, partial=True)
if serializer2.is_valid():
serializer2.save()
return Response(serializer2.data)
return Response(serializer.data)
def put(self, request, pk, format=None):
product = self.get_object(pk)
serializer = ProductSerializer(product, data=request.data, partial=True)
if serializer.is_valid():
serializer.save()
return Response(serializer.data)
return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)
def patch(self, request, pk):
product = self.get_object(pk)
serializer = ProductSerializer(product, data=request.data,
partial=True) # set partial=True to update a data partially
if serializer.is_valid():
serializer.save()
return JsonResponse(code=201, data=serializer.data)
return JsonResponse(code=400, data="wrong parameters")
def delete(self, request, pk, format=None):
product = self.get_object(pk)
product.delete()
return Response(status=status.HTTP_204_NO_CONTENT)
Thougths?
in put method change line
serializer = ProductSerializer(product, data=request.data, partial=True)
to be serializer = ProductSerializer(instance=product, data=request.data, partial=False)
also in patch method change line
serializer = ProductSerializer(product, data=request.data, partial=True)
to be
serializer = ProductSerializer(instance=product, data=request.data, partial=False)

Django Rest API Pagination not limiting number of objects

i followed the documentation but i still get the full list of objects not limited
views.py
from rest_framework.pagination import (LimitOffsetPagination,PageNumberPagination)
class PostView(generics.ListAPIView):
queryset = Post.objects.all()
serializer_class = PostSerializer
pagination_class = PageNumberPagination
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(serializer.data)
settings.py
REST_FRAMEWORK = {
'DEFAULT_PAGINATION_CLASS': 'rest_framework.pagination.PageNumberPagination',
'PAGE_SIZE': 2
}
By implementing the list method yourself, the pagination is no longer applied. Indeed, the list method for a ListAPIView is implemented as [GitHub]:
def list(self, request, *args, **kwargs):
queryset = self.filter_queryset(self.get_queryset())
page = self.paginate_queryset(queryset)
if page is not None:
serializer = self.get_serializer(page, many=True)
return self.get_paginated_response(serializer.data)
serializer = self.get_serializer(queryset, many=True)
return Response(serializer.data)
But you thus do not need to implement the list method yourself. Your view can look like:
from rest_framework.pagination import (LimitOffsetPagination,PageNumberPagination)
class PostView(generics.ListAPIView):
queryset = Post.objects.all()
serializer_class = PostSerializer
pagination_class = PageNumberPagination
# no list method

Permission depends on field django rest framework

I'm trying to write little project which is using django rest framework and I have the Book model and rest api for that:
#models.py
class Book(models.Model):
name = models.CharField(max_length=100)
language = models.CharField(max_length=3, choices=LANGUAGES)
publication_date = models.CharField(max_length=4)
class Meta:
verbose_name = 'Книга'
verbose_name_plural = 'Книги'
def __str__(self):
return self.name
The next serialiser:
#serializers.py
class BookSerializer(serializers.Serializer):
name = serializers.CharField(max_length=120)
language = serializers.CharField()
publication_date = serializers.CharField()
def create(self, validated_data):
return Book.objects.create(**validated_data)
def update(self, instance, validated_data):
instance.name = validated_data.get('name', instance.name)
instance.language = validated_data.get('language', instance.language)
instance.publication_date = validated_data.get(
'publication_date',
instance.publication_date
)
instance.save()
return instance
And the next view:
#views.py
class BookView(APIView):
http_method_names = ['get', 'post', 'put', 'delete']
def get(self, request):
books = Book.objects.all()
serializer = BookSerializer(books, many=True)
return Response({"books": serializer.data})
def post(self, request):
book = request.data.get('book')
# Create an article from the above data
serializer = BookSerializer(data=book)
if serializer.is_valid(raise_exception=True):
book_saved = serializer.save()
return Response({"success": "Book '{}' created successfully".format(book_saved.name)})
def put(self, request, pk, *args, **kwargs):
saved_book = get_object_or_404(Book.objects.all(), pk=pk)
data = request.data.get('book')
serializer = BookSerializer(instance=saved_book, data=data, partial=True)
if serializer.is_valid(raise_exception=True):
book_saved = serializer.save()
return Response({
"success": "Book '{}' updated successfully".format(book_saved.name)
})
def delete(self, request, pk, *args, **kwargs):
book = get_object_or_404(Book.objects.all(), pk=pk)
book.delete()
return Response(
{"message": "Book with id `{}` has been deleted.".format(pk)},
status=204
)
How I can to implement permission which depends on publication_date field, literally if publication_date more than current date, then get request is available only for admins.
Instead of using Book.objects.all() everywhere, define a function:
def get_queryset(self, request):
if request.user.is_superuser:
return Book.objects.all()
else:
return Book.objects.filter(publication_date__lte=datetime.datetime.now())
Then call it like self.get_queryset(request).
This will only make books published earlier than right now to be visible to non-admins.
Also, you have a lot of boilerplate code. You should probably be using ModelViewSet

How to write the functionality in generics.ListAPIView which can be written in APIView in Django DRF

I have a function base view which get 2 parameters from URL
http://127.0.0.1:8000/api/v1/contest/0b36d92a-51a7-4752-9df1-e5f2733116c1/paintings/
#api_view(['GET',])
#permission_classes([AllowAny])
def Contest_detail_by_id_and_category(request, id, category_name):
if request.method == 'GET':
artcontests = Artwork.objects.filter(artcontest = id,category__name__iexact=category_name)
serializer = ArtworkSerializer(artcontests, many=True)
# serializer = ArtworkSerializer(artcontests, many=True)
return Response(serializer.data)
which give proper result , but when I try to write the same functionality in generics.ListAPIView it gives
TypeError at /api/v1/contesty/0b36d92a-51a7-4752-9df1-e5f2733116c1/paintings/
object of type 'method' has no len()
class Contest_detail_by_id_category(generics.ListAPIView):
serializer_class = ArtworkSerializer1(many=True)
permission_classes = [AllowAny]
def queryset(self):
queryset = Artwork.objects.filter(artcontest = self.kwargs['id'],category__name__iexact=self.kwargs['category_name'])
# queryset = self.get_queryset()
serializer = ArtworkSerializer1(queryset)
return Response(serializer.data)
Can anyone help me - how to write the correct view in generics.ListAPIView or viewsets.ModelViewSet
Remove many=True from serializer_class. Create get_queryset function as below, instead of your queryset function.
class Contest_detail_by_id_category(generics.ListAPIView):
serializer_class = ArtworkSerializer1
queryset = Artwork.objects.all()
permission_classes = [AllowAny]
def get_queryset(self):
return self.queryset.filter(artcontest = self.kwargs['id'],category__name__iexact=self.kwargs['category_name'])
Update: Working:
ListAPIView class inherits ListModelMixin.
class ListModelMixin:
"""
List a queryset.
"""
def list(self, request, *args, **kwargs):
queryset = self.filter_queryset(self.get_queryset())
page = self.paginate_queryset(queryset)
if page is not None:
serializer = self.get_serializer(page, many=True)
return self.get_paginated_response(serializer.data)
serializer = self.get_serializer(queryset, many=True)
return Response(serializer.data)
See here. https://github.com/encode/django-rest-framework/blob/master/rest_framework/mixins.py

where the serializer in perform_createt comes from?

Although serializer as parameter passed in perform_create method, it is not clear where it pass from. Can anyone explain where the serializer parameter pass from and how it works?
class SnippetList(generics.ListCreateAPIView):
queryset = Snippet.objects.all()
serializer_class = SnippetSerializer
permission_classes = (permissions.IsAuthenticatedOrReadOnly,)
def perform_create(self, serializer):
serializer.save(owner=self.request.user)
It comes from the CreateModelMixin:
def create(self, request, *args, **kwargs):
serializer = self.get_serializer(data=request.data)
serializer.is_valid(raise_exception=True)
self.perform_create(serializer)
headers = self.get_success_headers(serializer.data)
return Response(serializer.data, status=status.HTTP_201_CREATED, headers=headers)

Resources