string list as a parameter in url in django - django

i am working in a search view which take two inputs as a filter :
search word
2.cities (multi select)
i did it using serializer and it worked but paggination does not work because it was a post method , so i'm trying to take these inputs from url parameters , i tried this pattern :
path(r"search/<str:search>/(?P<city>\w*)/",
SearchView.as_view({"get": "search"}), name="search"),
but when i browse : http://127.0.0.1:8000/company/search/taxi/montrial/
it return Not Found: /company/search/
so how to pass the paramteres or is there another way to use paggination with post method

I suggest to use pagination with get request or in case you should do it with post
class CustomPagination(pagination.PageNumberPagination):
def get_paginated_response(self, data):
return Response({
'links': {
'next': self.get_next_link(), #you can read page number from url and put it here
'previous': self.get_previous_link()
},
'count': self.page.paginator.count,
'results': data
})
to read data from url you can use request.query_params
https://www.django-rest-framework.org/api-guide/pagination/

i solved it using the serializer method, inherit from viewsets.GenericViewsets which has pagingation methods
class SearchView(viewsets.GenericViewSet):
permission_classes = [IsDriver]
queryset = CompanyProfile.objects.all()
serializer_class = SearchSerializer
#action(methods=['post'], detail=False)
def search(self, request, format=None):
serializer = SearchSerializer(data=request.data)
serializer.is_valid(raise_exception=True)
page = self.paginate_queryset(serializer.data["companies"])
if page is not None:
# used CompanyProfileSerializer to serialize the companies query
serializer = CompanyProfileSerializer(page, many=True)
return self.get_paginated_response(serializer.data)
return Response(serializer.data)

Related

Testing custom action on a viewset in Django Rest Framework

I have defined the following custome action for my ViewSet Agenda:
class AgendaViewSet(viewsets.ModelViewSet):
"""
A simple viewset to retrieve all the Agendas
"""
queryset = Agenda.objects.all()
serializer_class = AgendaSerializer
#action(detail=False, methods=['GET'])
def get_user_agenda(self, request, pk=None):
print('here1')
id = request.GET.get("id_user")
if not id:
return Response("No id in the request.", status=400)
id = int(id)
user = User.objects.filter(pk=id)
if not user:
return Response("No existant user with the given id.", status=400)
response = self.queryset.filter(UserRef__in=user)
if not response:
return Response("No existant Agenda.", status=400)
serializer = AgendaSerializer(response, many=True)
return Response(serializer.data)
Here, I'd like to unit-test my custom action named "get_user_agenda".
However, when I'm testing, the debug output("here1") doesn't show up, and it always returns 200 as a status_code.
Here's my test:
def test_GetUserAgenda(self):
request_url = f'Agenda/get_user_agenda/'
view = AgendaViewSet.as_view(actions={'get': 'retrieve'})
request = self.factory.get(request_url, {'id_user': 15})
response = view(request)
self.assertEqual(response.status_code, 400)
Note that:
self.factory = APIRequestFactory()
Am I missing something?
Sincerely,
You will have to use the method name of the custom action and not retrieve so:
view = AgendaViewSet.as_view(actions={'get': 'get_user_agenda'})
You have to specify request url
#action(detail=False, methods=['GET'], url_path='get_user_agenda')
def get_user_agenda(self, request, pk=None):
And in my opinion it would be better to use detail=True, and get pk from url.
For example: 'Agenda/pk_here/get_user_agenda/'

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.

DRF YASG Customizing

I'm trying to customize my api documentation buuild with yasg.
First off, I would like to determine the naming of my own sections, and what endpoints should be included in this section. It seems that the naming of sections is based on the first prefix not belonging to the longest common prefix e.g.:
if we have the urls api/v1/message and api/v1/test than the sections will be named message and test. Is there a way for me to determine A custom naming for this section?
Furthermore, the introduction of every section is empty, how do I add text here?
And last but not least, Stripe has these amazing section dividers how can I add these in drf yasg.
Currently, I'm using APIView and #swagger_auto_schema to define the documentation of my endpoint.
In the code below, you can see how to add more information to define your endpoint. I hope it helps you
##serializers.py
class CategorySerializer(serializers.ModelSerializer):
"""
Serializing Categories
"""
class Meta:
model = Category
fields = [
'id', 'name', 'slug'
]
read_only_fields = [
'slug',
]
##views.py
username_param = openapi.Parameter('username', in_=openapi.IN_QUERY, description='Username',
type=openapi.TYPE_STRING)
email = openapi.Parameter('email', in_=openapi.IN_QUERY, description='Email',
type=openapi.TYPE_STRING)
category_response = openapi.Response('response description', CategorySerializer)
class CategoryList(APIView):
permission_classes = [AllowAny]
#swagger_auto_schema(
manual_parameters=[username_param, email],
query_serializer=CategorySerializer,
responses = {
'200' : category_response,
'400': 'Bad Request'
},
security=[],
operation_id='List of categories',
operation_description='This endpoint does some magic',
)
def get(self, request, format=None):
"""
GET:
Return a list of all the existing categories.
"""
categories = Category.objects.all()
serializer = CategorySerializer(categories, many=True)
return Response(serializer.data)
#swagger_auto_schema(
request_body=CategorySerializer,
query_serializer=CategorySerializer,
responses={
'200': 'Ok Request',
'400': "Bad Request"
},
security=[],
operation_id='Create category',
operation_description='Create of categories',
)
def post(self, request, format=None):
"""
POST:
Create a new category instance.
"""
serializer = CategorySerializer(data=request.data)
if serializer.is_valid():
serializer.save(created_by=self.request.user)
return Response(serializer.data, status=status.HTTP_201_CREATED)
return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)
By last, if you want to see your endpoints in groups by link, you can test commenting the line below in yours urls.py
#urlpatterns = format_suffix_patterns(urlpatterns)
Below, some screen of how you should see it
You can find more information in the following link: https://drf-yasg.readthedocs.io/en/stable/custom_spec.html

Removing the primary key in class based views ( django rest framework )

Problem :
Currently in my api/urls.py I have this line
url(r'^profile/(?P<pk>[0-9]+)/$', views.UserProfileView.as_view()),
but I want to get the profile based on request.user and so I have the code in class UserProfileView as the following :
class UserProfileView(generics.RetrieveUpdateAPIView):
serializer_class = UserProfileSerializer
permission_classes = (permissions.IsAuthenticatedOrReadOnly,
IsOwnerOrReadOnly,)
pagination_class = LimitTenPagination
def get_queryset(self):
try:
queryset = UserProfile.objects.filter(user=self.request.user)
except:
raise APIException('No profile linked with this user')
return queryset
But If I remove the pk field from urls.py file, I get an error like this :
AssertionError at /api/profile/
Expected view UserProfileView to be called with a URL keyword argument
named "pk". Fix your URL conf, or set the .lookup_field attribute on
the view correctly.
Which is expected.
Possible solution :
I made a function based view like this :
#api_view(['GET', 'PUT'])
def user_detail(request):
"""
Retrieve, update or delete a code snippet.
"""
try:
user_profile_data = UserProfile.objects.get(user=request.user)
except:
raise APIException('No profile linked with this user')
if request.method == 'GET':
serializer = UserProfileSerializer(user_profile_data)
return Response(serializer.data)
elif request.method == 'PUT':
serializer = UserProfileSerializer(user_profile_data, data=request.data)
if serializer.is_valid():
serializer.save()
return Response(serializer.data)
return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)
And in the urls.py file added this line :
url(r'^me/$', views.user_detail),
This gets the work done, but I want a class based solution, so that in case I needed to use pagination_class, permission_class and other features of drf, I can easily use it.
As of now, since I need to fetch only one object, so pagination is out of question.
Thanks.
It is get_object that you need to override for a detail-based view rather than get_queryset.

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.