Add context to head of ListAPIView - django

I would like to add {'user': self.request.user} to the header of my ListAPIView.
For example, the JSON response would look like:
[
'user': testing_user,
{
id: 67,
slug: "slugy",
},
]
Is it possible to do this with this view?
class BookArchiveAPIView(generics.ListAPIView):
cache_timeout = 60 * 7
serializer_class = BookSerializer
queryset = Book.objects.all()

I know its too late but I needed same thing and just found on the documentation easily.
You don't need to pass the "request.user" to the view because DRF already gives it to you.
Here is the example:
class show_activities(generics.ListAPIView):
serializer_class = ActivitySerializer
def get_queryset(self):
username = self.request.user.username
return Activity.objects.filter(owner=username)
It's easy to find on documentation but I'm answering for the ones who couldn't see it.

Related

Django rest-framework ListCreateAPIView doesn't return the full path for the file

I just started using Django rest-framework, and I wanted the JSON response to return the URL for the file stored in the server, I used generics.ListCreateAPIView class
class PeopleList(generics.ListCreateAPIView):
queryset = People.objects.all().order_by('registration_time')
serializer_class = PeopleSerializer
and it worked actually! it returned a full path clickable URL:
{
"id": 1,
...
"profile": "http://127.0.0.1:8000/media/profile_image/IMG_20190826_105834_vwKLf10.jpg",
...
},
But then I had to use the list function because I needed the request object.
class PeopleList(generics.ListCreateAPIView):
queryset = People.objects.all().order_by('registration_time')
serializer_class = PeopleSerializer
def list(self, request, *args, **kwargs):
if self.request.user.is_authenticated:
queryset = People.objects.all().order_by('registration_time')
serializer_class = PeopleSerializer(queryset, many=True)
print(serializer_class.data)
return Response(serializer_class.data)
else:
content = {'Authentication Error': 'Please Login!'}
return Response(content, status.HTTP_405_METHOD_NOT_ALLOWED)
Now the response only contains the path without the domain:
{
"id": 1,
...
"profile": "/media/profile_image/IMG_20190826_105834_vwKLf10.jpg",
...
},
so the question is, How can I return the whole URL?
Add request to the context passed to your serializer:
serializer_class = PeopleSerializer(queryset, many=True, context={'request':request})
Use Adel's answer. If you ever need to access the absolute URI, then request.build_absolute_uri() should suffice.

How to use lookup_field along with FilterSet in Django Rest Framework?

I am working on developing an API for a project and it contains a course list page where
All the courses offered by a particular college is listed.
Should support retrieving of a specific course using lookup_field as course_slug.
Supports filtering courses based on some other parameters which I am implementing with the help of Django FilterSet along with filterset_class (filterset_class).
The problem is, once the courses are filtered based on the requirements, the query params is included in the URL as well. Now, I am not able do the retrieve operation on it, as the URL cannot recognize the lookup_field added and mistakes it to a pattern in query params.
ViewSet.py
class CollegeCourseViewSet(viewsets.ReadOnlyModelViewSet):
serializer_class = CourseSerializer
lookup_field = 'slug'
filter_backends = (SearchFilter, DjangoFilterBackend,)
search_fields = ['name']
filterset_class = CourseFilter
def get_queryset(self):
queryset = Course.objects.all()
return queryset.filter(college_courses__college__slug=self.kwargs['college_slug'])
def get_object(self):
obj = super().get_object()
if self.action == 'retrieve':
obj.additional = CollegeCourse.objects.filter(course=obj, college__slug=self.kwargs['college_slug']).first()
return obj
def get_serializer_class(self):
if self.action == 'retrieve':
return CollegeCourseSerializer
return super().get_serializer_class()
filters.py
class CourseFilter(filters.FilterSet):
name = filters.MultipleChoiceFilter(field_name='college_courses__stream__name',
choices=choices("STREAM_NAME_CHOICES"))
category = filters.MultipleChoiceFilter(field_name='college_courses__stream__category',
choices=choices("STREAM_CATEGORY_CHOICES"))
program = filters.MultipleChoiceFilter(field_name='college_courses__stream__program',
choices=choices("STREAM_PROGRAM_CHOICES"))
class Meta:
model = Course
fields = [
'name',
'category',
'program'
]
urls.py
router = routers.DefaultRouter()
router.register(r'courses', CollegeCourseViewSet, basename='college_course')
urlpatterns = [
path('api/college/v1/colleges/<slug:college_slug>/', include(router.urls)),
]
Suppose I filter based on category = Agriculture, I get a list of three courses. Now, I would like to retrieve one of the courses using its slug. But here is the Bad Request message
URL
GET /api/college/v1/colleges/clslug1/courses/?category=Agriculture/crslug2
crslug2 is the slug of the course.
Message
"Select a valid choice. Agriculture/crslug2 is not one of the available choices."
Is there any way to add the lookup_field after the query params?
Thanks in advance.
Okay so answering my own post. Basically everything was fine except that the URL was not RESTful.
The lookup_field must be given before any query params in RESTful apis.
So the correct format is
GET /api/college/v1/colleges/clslug1/courses/crslug2/?category=Agriculture

List pagination in Django

I want to paginate a list of books. The api end point is like this /books?page=num&size=num
So the number of page and how many books to load will be variables.
My response should look like:
{
pagesnum= totalpages
booksnum=totalbooks
books=
[ {detailsofbook1},
{...},
{...}
]
}
My code:
urlpattern:
path('api/books?page=<int:page>&size=<int:size>/',BookView.as_view()),
views.py
class BookView(APIView):
http_method_names = ['get']
permission_classes = (permissions.IsAuthenticated,)
def index(request):
books = Books.objects.all()
books_per_page= request.GET.get('size')
book_paginator = Paginator(books, books_per_page)
page_num = request.GET.get('page')
page = book_paginator.get_page(page_num)
context = {
'count': book_paginator.count(),
'page': page
queryset =Books.ojects.all()
}
return JsonResponse(context, status=200)
This implementation doesn't work. First of all something is wrong probably with the url it doesnt seem to understand the variables. Or is something else wrong with my code?
page and size are query parameters, so you get them by accessing request.query_params so it should be like
books_per_page= request.query_params.get('size') # or request.query_params['size]
page_num = request.query_params.get('page')
also, I'm a bit confused with index() I think your code should be in get(self, request, *args, **kwargs)
and my last thing is that you can make use of pagination in DRF, see

Nesting ViewSet routes in DRF

I've created 2 ModelViewSets like so (simplified for demonstration):
class SomeBaseViewSet(viewsets.ReadOnlyModelViewSet):
serializer_class = SomeEventSerializer
permission_classes = (permissions.IsAuthenticated,)
def get_queryset(self):
return SomeObjects.objects.filter(owner=self.request.user)
def get_serializer_context(self):
context = super(SomeBaseViewSet, self).get_serializer_context()
context.update({
"user": self.request.user,
"some_context_key": False
})
return context
class AdminViewSet(SomeBaseViewSet):
# Added in the HasAdminPermission
permission_classes = (permissions.IsAuthenticated, HasAdminPermission)
# Different queryset filter (overriding `get_queryset` vs setting queryset for standardization)
def get_queryset(self):
return SomeObjects.objects.all()
# The context should have `some_context_key=True`, and `user=request.user`
def get_serializer_context(self):
context = super(AdminViewSet, self).get_serializer_context()
context.update({
"some_context_key": True
})
return context
My router/url config looks like this
router = DefaultRouter()
router.register(r'some_view', SomeBaseViewSet, base_name="some_view")
urlpatterns += [
url(r'^api/', include(router.urls)),
]
If I wanted to route /api/some_view/admin to the AdminViewSet, what's the best way to do that?
Things I've tried:
#list_route on SomeBaseViewSet, but couldn't figure out the "right" way to wire it to my AdminViewSet
Adding url(r'^api/some_view/admin$', AdminViewSet.as_view({"get": "list"})) to my urlpatterns (which sorta works but neuters the ViewSet a bit, and is generally really manual anyway):
Having a dedicated DefaultRouter for the some_view viewset, which I then mount on url(r'^api/some_view/') - again hacky and pedantic to do with a large number of routes
Is there a "right" way to do what I'm trying to accomplish, or should I reach for a different solution to this problem (i.e. a filter or something)?
I've seen libraries like https://github.com/alanjds/drf-nested-routers, but that seems like overkill for my (fairly simple) needs.
Not sure if i've missed something but i've just tested and this works perfectly (the order is important):
router = DefaultRouter()
# this will overrides routes from the line below
router.register(r'some_view/admin', AdminViewSet)
router.register(r'some_view', SomeBaseViewSet)
Define your admin viewset using a list route. These params will allow you to perform a get request, with specified permissions(is authenticated and has admin perms), that extends this class. ie /someview/admin or someotherview/admin
from rest_framework.decorators import list_route
class AdminViewSet(viewset.Viewsets):
#list_route(methods=['get'],
permissions=[permissions.IsAuthenticated, HasAdminPermission],
url_path='admin'
)
def admin(self, request):
# All your custom logic in regards to querysets and serializing
return serialized.data
Then you can extend your any viewset that needs an admin action route.
class SomeBaseViewSet(viewsets.ReadOnlyModelViewSet, AdminViewset):
serializer_class = SomeEventSerializer
permission_classes = (permissions.IsAuthenticated,)
def get_queryset(self):
return SomeObjects.objects.filter(owner=self.request.user)
def get_serializer_context(self):
context = super(SomeBaseViewSet, self).get_serializer_context()
context.update({
"user": self.request.user,
"some_context_key": False
})
return context
You want to be careful with this because typically the param after your base route ie /someview/{param}/ is reserved for ID references. Make sure your id reference will not conflict with your detail route.
I think I've found an answer to my own question, but I put a +50 rep bounty on this in case someone wants to chime in (#tom-christie maybe?).
Either way the way that I've solved it for my usecase is by using the #list_route and the AdminViewSet's .as_view() function.
Something like this suffices:
class SomeBaseViewSet(viewsets.ReadOnlyModelViewSet):
serializer_class = SomeEventSerializer
permission_classes = (permissions.IsAuthenticated,)
def get_queryset(self):
return SomeObjects.objects.filter(owner=self.request.user)
def get_serializer_context(self):
context = super(SomeBaseViewSet, self).get_serializer_context()
context.update({
"user": self.request.user,
"some_context_key": False
})
return context
#list_route()
def admin(self, request):
return AdminViewSet.as_view({"get": "list"})(request)
class AdminViewSet(SomeBaseViewSet):
# Added in the HasAdminPermission
permission_classes = (permissions.IsAuthenticated, HasAdminPermission)
# Different queryset filter (overriding `get_queryset` vs setting queryset for standardization)
def get_queryset(self):
return SomeObjects.objects.all()
# The context should have `some_context_key=True`, and `user=request.user`
def get_serializer_context(self):
context = super(AdminViewSet, self).get_serializer_context()
context.update({
"some_context_key": True
})
return context
And will allow one to have the url's routed accordingly (based on the name of the function), and enforce any extra things you need.
good question. i'd check out DRF's detail_route for this -- this is an idiom i've used successfully in the past to create a one-off type of endpoint that hangs off of a viewset. HTH,
http://www.django-rest-framework.org/api-guide/routers/#extra-link-and-actions

How to paginate response from function based view of django rest framework?

I have written a code snippet like below, i need to achieve pagination in this, kindly let me know how is it possible. Also due to some reasons i want to use only function based views.
#api_view(['GET'])
#permission_classes([AllowAny])
def PersonView(request):
context={'request': request}
person_objects = Person.objects.all()
if len(person_objects) > 0:
person_data = PersonSerializer(person_objects, many=True, context=context)
return Response(person_data.data, status=status.HTTP_200_OK)
else:
return Response({}, status=status.HTTP_200_OK)
http://www.django-rest-framework.org/api-guide/pagination/
from rest_framework.pagination import PageNumberPagination
#api_view(['GET',])
#permission_classes([AllowAny,])
def PersonView(request):
paginator = PageNumberPagination()
paginator.page_size = 10
person_objects = Person.objects.all()
result_page = paginator.paginate_queryset(person_objects, request)
serializer = PersonSerializer(result_page, many=True)
return paginator.get_paginated_response(serializer.data)
You can also define custom pagination class by overriding PageNumberPagination
pagination.py
from rest_framework import pagination
class StandardResultsSetPagination(pagination.PageNumberPagination):
page_size = 10
page_query_param = 'page'
page_size_query_param = 'per_page'
max_page_size = 1000
it will help to define
page_size, page query custom parameters and max_page_size
views.py
from rest_api.pagination import StandardResultsSetPagination
#api_view(['GET',])
#permission_classes([AllowAny,])
def PersonView(request):
person_objects = Person.objects.all()
if len(person_objects)> 0:
paginator = StandardResultsSetPagination()
result_page = paginator.paginate_queryset(person_objects, request)
serializer = PersonSerializer(result_page, many=True)
return paginator.get_paginated_response(serializer.data)
else:
return Response({},status=status.HTTP_200_OK)
Eg:
Request
GET https://api.example.org/persons/?page=1&per_page=10
Response
HTTP 200 OK
{
"count": 1023
"next": "https://api.example.org/persons/?page=2&per_page=10",
"previous": null,
"results": [
…
]
}
The ACCEPTED answer has a BUG
A ModelSerializer accepts a queryset or an object, in the accepted answer the PersonSerializer is given the ouput of paginator.paginate_queryset which returns a list containing single element of class Page and thus the serializer will either return incomplete data or no data at all(know this because have tried and seen wrong results).
This can be easily fixed by passing the actual queryset to the serializer which in this case will be person_objects,
So the final code will be,
from rest_framework.pagination import PageNumberPagination
#api_view(['GET',])
#permission_classes([AllowAny,])
def PersonView(request):
paginator = PageNumberPagination()
paginator.page_size = 10
person_objects = Person.objects.all()
result_page = paginator.paginate_queryset(person_objects, request)
serializer = PersonSerializer(person_objects, many=True) # MAIN CHANGE IS HERE
return paginator.get_paginated_response(serializer.data)
This fixes the bug but passing the complete queryset to serializer will take lot of time for serializing since serializer works on lazy approach, but this performance issue is in itself a new question.