Django Rest Framework request as parameter - python

I'm learning Django rest Framework. I know how to use Class Based Views. Why methods GET, PUT, DELETE, ETC needs the "request" parameter even if the object itself contains the same request attribute?
For example, in this code:
class ArticleDetailViewAPIView(APIView):
def get_object(self, pk):
object = get_object_or_404(Article, pk=pk)
return object
def get(self, request, pk):
object = self.get_object(pk=pk)
serializer = SerializerArticle(object)
return Response(serializer.data)
For example, if in the get method I put the line:
print("Equal: ", self.request is request)
In the console, the output is TRUE

Related

Setting pagination_class in Django ListAPIView causes Runtime Error

I have a class-based view for text messages inheriting from the django generic view ListAPIView. As you can probably tell from the inheriting class name, the view is being used as an API with pagination (from the Django Rest framework).
I would like to turn off pagination for some specific queries, however even when I try turning off pagination for all queries via this stack overflow question (Turn off automatic pagination of Django Rest Framework ModelViewSet), I get the following error:
RuntimeError: Do not evaluate the `.queryset` attribute directly, as the result will be cached and reused between requests. Use `.all()` or call `.get_queryset()` instead.
I am overwriting the get_queryset() method in the view without issue, however by just setting the paginator_class variable to None I am receiving this error. Any help would be appreciated. Here's some code:
view.py:
class TextMessageView(generics.ListAPIView):
queryset = TextMessage.objects.all()
serializer_class = TextMessageSerializer
pagination_class = None
def get_queryset(self):
"""
If a phone number is included in the url query, return only the text messages sent by that number in the
queryset, otherwise return all text messages.
:return: A Django queryset with a variable number of text messages.
"""
from_phone_num = self.request.query_params.get('from_phone_number', None)
distinct_nums = self.request.query_params.get('distinct_numbers', None)
all_msgs = self.request.query_params.get('all_messages', None)
if from_phone_num:
return TextMessage.objects.filter(from_phone_number=from_phone_num)
elif distinct_nums == 'True':
return TextMessage.objects.order_by('from_phone_number', '-date_of_message').distinct('from_phone_number')
elif all_msgs == 'True':
return self.queryset
else:
raise Http404
Django Rest Frameworks: ListAPIView.py
class ListAPIView(mixins.ListModelMixin,
GenericAPIView):
"""
Concrete view for listing a queryset.
"""
def get(self, request, *args, **kwargs):
return self.list(request, *args, **kwargs)
Django Rest Frameworks: mixins.py
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)
you'll have to look up GenericAPIView.py because it's too big
Looking at the error message, in your get_queryset method, can you try changing
elif all_msgs == 'True':
return self.queryset
to
elif all_msgs == 'True':
return self.queryset.all()
?

How to send HTTP response in Json format in Generic Class Views [Django]

I have sub-classed Generic DetialView class in views.py and trying to figure out a way to return data in JSON format based on an argument received in the url. Here's what I have tried doing...
# views.py
from django.views.generic import DetailView
from django.http import JsonResponse
class ExtendedView(DetailView):
context_object_name = 'post'
model = StorageModel
template_name='posts.html'
def get_context_data(self, **kwargs):
data = super(HacksViewPost, self).get_context_data(**kwargs)
if bool(self.request.GET):
data__ = JsonForm(request.GET)
if data__.is_valid():
json = data__.cleaned_data['json']
if json == 'true':
return JsonResponse({'data': 'data'})
return data
But this gave me TypeError as it should be:
TypeError at /category/extended-slug/
context must be a dict rather than JsonResponse.
The url that activates the ExtendedView class is:
/category/extended-slug?json=true
So, the question is how could i send data in JSON Format from a Generic View Class and are there any better ways of acheiving this?
I think you patch it at the wrong level. The get_context_data is used by the get function to render it. As a result, the get_context_data object has no control about what is done with the result, in order to construct a server response,
You can however patch the get(..) function like:
class ExtendedView(DetailView):
"""A base view for displaying a single object."""
def get(self, request, *args, **kwargs):
self.object = self.get_object()
data = self.get_context_data(object=self.object)
if self.request.GET:
data__ = JsonForm(request.GET)
if data__.is_valid():
json = data__.cleaned_data['json']
if json == 'true':
return JsonResponse({'data': data})
return self.render_to_response(data)
The same holds for post, put, and other requests.
If we take a look at the DetailView source code we see:
class BaseDetailView(SingleObjectMixin, View):
"""A base view for displaying a single object."""
def get(self, request, *args, **kwargs):
self.object = self.get_object()
context = self.get_context_data(object=self.object)
return self.render_to_response(context)
Hence the get(..) calls the get_context_data(..) function. But it does not immediately returns the result, it wraps it into a rendered response.
You can't return a JsonResponse inside the get_context_data method. The get_context_data method allows you to send extra information (context) to the template and is expected to return a dict, not a JsonResponse.
If you want to return a JsonResponse, do that in the get or post method of your class.

"detail": "Method \"GET\" not allowed. on calling endpoint in django

I'm using django.rest_framework. I have a get_or_create method for a particular view,
class LocationView(views.APIView):
def get_or_create(self, request):
try:
location = Location.objects.get(country=request.data.get("country"), city=request.data.get("city"))
Response(location, status=status.HTTP_200_OK)
except Location.DoesNotExist:
serializer = LocationSerializer(data=request.data)
if serializer.is_valid():
serializer.save()
return Response(serializer.data, status=status.HTTP_201_CREATED)
else:
Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)
This is the location Model,
class Location(models.Model):
country = models.CharField(max_length=255)
city = models.CharField(max_length=255, unique=True)
latitude = models.CharField(max_length=255)
longitude = models.CharField(max_length=255)
class Meta:
unique_together = ('country', 'city')
This is my url,
url(r'^location/$', LocationView.as_view(), name='location'),
When I call this endpoint in the following way,
http://127.0.0.1:8000/api/v1/bouncer/location/?country=USA&&city=Sunnyvale&&latitude=122.0363&&longitude=37.3688
This is what I get,
{
"detail": "Method \"GET\" not allowed."
}
What am I missing here.
The Method not allowed error is because, it searches for a get() method inside your API class, and it couldn't find a one.
The general format of the API class is as below
class LocationView(views.APIView):
def get(self, request):
#do something with 'GET' method
return Response("some data")
def post(self, request):
#do something with 'POST' method
return Response("some data")
If you want to invoke the get_or_create() method at some point, you can do it as any other methods,
class LocationView(views.APIView):
def get_or_create(self, request):
# do some "get or create" stuff
return "some data"
def get(self, request):
if condition:
self.get_or_create(request)
# do some stuff
return Response(" some special data related to get or create")
return Response("some data")
You need to provide separate methods for get and post. If your get method also creates an instance, then you can just call your current get_or_create inside both get and post methods.
class LocationView(views.APIView):
def get_or_create(self, request):
# your current definition here
def get(self, request):
return self.get_or_create(request)
def post(self, request):
return self.get_or_create(request)
For my situation, I was sending a request properly as my view would expect, a POST. But the issue was on http/s. I had set a permanent redirect, from http to https on my app, and so sending a POST to the http version resulted in a GET somehow when the server was redirecting.
TL;DR
HTTP instead of HTTPS

Override update method of UpdateAPIView

I need to do some actions, before calls update().
my code
class CarView(generics.UpdateAPIView):
permission_classes = (IsAdminUser,)
serializer_class = CarSerializer
def get_queryset(self):
return ...
def update(self, request, *args, **kwargs):
# some actions
super(CarView, self).update(request, *args, **kwargs)
But I'm getting an error
error message
Expected a Response, HttpResponse or HttpStreamingResponse to be
returned from the view, but received a <type 'NoneType'>
How can I fix that?
Like most Django views, your update method on the ViewSet should be returning a response. Right now you aren't returning anything, which is why Django is complaining about receiving NoneType (as that is the default return value).
The issue is coming from the last line of your update method, where you are calling the parent update but aren't returning it.
super(CarView, self).update(request, *args, **kwargs)
If you returned it, the response that came from the update method that is normally defined would be passed down the chain and rendered as you would expect.
return super(CarView, self).update(request, *args, **kwargs)
This is happening because you have not returned anything in your update method. Django views expect a Response object to be returned. Just add a return in your update method.
class CarView(generics.UpdateAPIView):
permission_classes = (IsAdminUser,)
serializer_class = CarSerializer
def get_queryset(self):
return ...
def update(self, request, *args, **kwargs):
# some actions
return super(CarView, self).update(request, *args, **kwargs)
According to docs,
REST framework supports HTTP content negotiation by providing a
Response class which allows you to return content that can be rendered
into multiple content types, depending on the client request.
The Response class subclasses Django's SimpleTemplateResponse.
Response objects are initialised with data, which should consist of
native Python primitives. REST framework then uses standard HTTP
content negotiation to determine how it should render the final
response content.
So, to render the data into different content types, you have to return a response.

Django: ListView with post() method?

I am trying to process two forms in a Django class based view. The site contains a form called form (based on GET) for narrowing the list results of the ListView and the second form status_form (based on POST).
Both forms are required since the ListView returns a list of items. Form lets the user restrict the choices and status_forms lets the user flag incorrect items via a modal form (therefore it needs to be in the same template).
My trouble is that ListView does not come with the method post, however FormView does. My class List inherits from both classes, but when I execute the class I get the error message:
Attribute Error: 'List' object has no attribute 'status_form'
How should I change my implementation to allow the second form been processed via the post method?
class List(PaginationMixin, ListView, FormMixin):
model = ListModel
context_object_name = 'list_objects'
template_name = 'pages/list.html'
paginate_by = 10 #how may items per page
def get(self, request, *args, **kwargs):
self.form = ListSearchForm(self.request.GET or None,)
return super(List, self).get(request, *args, **kwargs)
def post(self, request, *args, **kwargs):
self.status_form = StatusForm(self.request.POST or None)
if self.status_form.is_valid():
...
else:
return super(List, self).post(request, *args, **kwargs)
def get_queryset(self):
# define the queryset
...
# when done, pass to object_list
return object_list
def get_context_data(self, **kwargs):
context = super(List, self).get_context_data(**kwargs)
context.update(**kwargs)
context['form'] = self.form
context['status_form'] = self.status_form # Django is complaining that status_form is not existing, result since the post method is not executed
return context
# Django is complaining that status_form does not exist,
# result since the post method is not executed
context['status_form'] = self.status_form
Because you didn't define self.status_from in the first place.
You have defined it in get_context_data, and it's accessible from there.
You can access you object from get_context_data in your post method;
context = self.get_context_data(**kwargs)
status_form = context['status_form']
Also consider that you can define your status_form directly in post method itself without getting it from self or get_context_data.
Redesign you views to separate each Form processing in separate Views then tight them with each-other.
Views redesign:
In nutshell, let each view to do one job. You can create a View just for processing your status_form and name it like StatusFormProcessView then on your List view return it on its post method
class List(ListView);
def post(self, request, *args, **kwargs):
return StatusFormView.as_view()(request) # What ever you need be pass to you form processing view
This is just an example of it, need more work to be real.
For another example; On my website index page I have a search form. when user POST or GET the search form, The processing of searching doesn't exist in my IndexView, instead I handle the whole form stuff in separate view, If form should process on GET method, I'll override get() method, If form should process on POST, I'll override post() method to send search_form data to the view that is responsible for handling of processing the search_form.
Comments response
status_form = context['status_form']
shouldn't it be
context['status_form'] = status_form
after I created it ?
You want to get status_form from context, So you need to
status_form = context['status_form']
Anyway, your form data are available on self.request.POST

Categories