drf spectacular not showing query params in swagger ui - django

I have generic ListAPIView with following list request function. Here category is a query parameter. I am trying to achieve such that the swagger ui also shows the query parameter in the swagger ui doc. How to achieve this? I have the following code.
#extend_schema(
parameters=[
OpenApiParameter(name='category',location=OpenApiParameter.QUERY, description='Category Id', required=False, type=int),
],
)
def list(self, request, *args, **kwargs):
category_id = request.query_params.get('category')
....
return Response(data)

The correct entrypoint for annotations on ApiView (here ListAPIView) is the get method, not the list method.
This is because get is the actual method and list is only wrapped to proxy to the mixin.
class ListAPIView(mixins.ListModelMixin, GenericAPIView):
def get(self, request, *args, **kwargs):
return self.list(request, *args, **kwargs)
FAQ entry.
Annotate the get method and call self.list() yourself, and the parameter will show up.

I agree with Insa but you can also decorate your View,
to avoid implement/override get method just to decorate it.
#extend_schema_view(
get=extend_schema(
parameters=[
OpenApiParameter(name='category', description='Category Id', type=int),
]
)
)
class YourListView(ListAPIView):
...
I dropped OpenApiParameter location and required arguments as they equal default values.

Related

write custom functions inside generic class based view in django rest framework

I have to write a custom function inside class based view which is like below:
class ExampleView(ListCreateAPIView,
UpdateAPIView,
DestroyAPIView):
queryset = Example.objects.all()
serializer_class = ExampleSerializer
def get(self, request, *args, **kwargs):
/.........some code............./
def random_custom(self, request, *args, **kwargs):
/.........some code............./
Here above I have a random custom function which I need to call from the url. Now I am not sure how to do that. If it was a modelviewset, we can do it easily like this:
path("example/",users.ExampleView.as_view({"get": "random_custom"}),
),
I have done it before in ModelVIewset,ie call custom functions like above but I am not sure how to do that is genericviews like above.
Update
def dispatch(self, request, *args, **kwargs):
if request.method == 'PUT' and kwargs.get('slug'):
return self.custom_function(request,*args,**kwargs)
return super().dispatch(request, *args, **kwargs)
def custom_function(self, request, *args, **kwargs):
instance = self.get_object()
print(instance)
slug = kwargs.get("slug")
print(slug)
print(request.data)
Here I can see that from dispatch, the custom function is called and I can see the print of instance and slug but when I print(request.data) it says the error below:
AttributeError: 'ExampleView' object has no attribute 'data'
I need request.data to perform some logic in the data.
Have you tried putting random_custom() into get() method? It should be executed right after GET method is initialised by client.
class ExampleView(...):
...
def get(self, request, *args, **kwargs):
self.random_custom()
/.........some code............./
def random_custom(self, request, *args, **kwargs):
/.........some code............./
The code which makes the decision about which method on your APIView-derived class to call is in APIView.dispatch(). You can read the source here.
Here's how it works:
Convert the method name (e.g. GET, POST, OPTIONS) to lowercase. Check that the method name is a valid HTTP method. If not, return an error code.
Check if the object defines a lowercase version of the method name. If not, return an error code.
Use getattr() to get that method, and call it.
This means that there are only two ways to redirect the call from get().
Define get() and make it call random_custom(), as #NixonSparrow describes.
Override the dispatch() method to call something else.
Those are the only two ways to do it.

Can I make a Django mixin that adds context variables just before template render?

I have a Django application that uses a JSON API as its data source.
Here's a simplified example of use in one of my views.py:
class GroupsList(LoginRequiredMixin):
def get(self, request, **kwargs):
# Get file list and totals
try:
group_list = group_adapter.list() # makes an API call and ALSO populates a meta info class
except APIAccessForbidden:
return HttpResponseRedirect(reverse('logout'))
return render(request, 'groups/index.html', {
# can I make a mixin to add data here gained from the API call?
'group_list': group_list,
})
This line:
The group_adapter.list() call populates some meta information into another class, that's not related to the group_list itself. I'd like to pass that data to the template. Ordinarily I'd use a context_processor, but when the context processor is called, the API call hasn't been made yet. I could manually check the information and add it to the render() method, but then I'd need to do that in dozens of different views.
Potential Solution #1: Create a Mixin For It
Can I use a mixin here that adds this information to context AFTER the view code runs but BEFORE render passes information to the template?
In other words is there a way to do this:
class GroupsList(LoginRequiredMixin, AddMetaInfoToContextMixin):
and then create a mixin something like this?
class AddMetaInfoToContextMixin(ContextMixin):
def get_context_data(self, **kwargs):
# self.request
context = super().get_context_data(**kwargs)
context['global_meta_information'] = get_global_meta_information()
return context
Potential Solution #2: Make an overridden templateview
Commenter Melvyn pointed out that I can potentially subclass TemplateView and override get_context_data(), so would something like this work?
class TemplateViewWithMeta(TemplateView):
def get_context_data(self, *args, **kwargs):
context = super(Home. self).get_context_data(*args, **kwargs)
context['global_meta_information'] = get_global_meta_information()
return context
class GroupsList(LoginRequiredMixin, TemplateViewWithMeta):
[...]
The typical workflow for a Django generic TemplateView is:
get()
get_context_data()
render_to_response()
So in your case keeping with the spirit of generic views, you could do it like this:
from django.views import generic
class BaseRemoteApiView(generic.TemplateView):
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self.group_list = None
def get(self, request, *args, **kwargs):
try:
self.group_list = group_adapter.list() # makes an API call and ALSO populates a meta info class
except APIAccessForbidden:
return HttpResponseRedirect(reverse('logout'))
return super().get(request, *args, **kwargs)
class RemoteApiContextMixin(generic.base.ContextMixin):
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
context["group_list"] = self.group_list
context["meta_information"] = get_global_meta_information()
return context
class ConcreteRemoteApiView(RemoteApiContextMixin, BaseRemoteApiView):
pass
Of course, you don't have to make 3 classes and can just combine the 3 into one - depends on how mixable you want to be.

adding metadata to filtering in django rest framework

I have a generic ListCreateAPIView view. I've implemented a get_queryset function that performs a search. The function parses the query, extract tags and terms and returns a query set.
def get_queryset(self):
query = self.request.QUERY_PARAMS.get('query', None)
# No deleted items
queryset = Items.objects.filter(deleted__isnull=True)
if query is None:
return queryset
predicates = []
# Generate predicates from query
queryset = queryset.filter(reduce(__and__,predicates))
return queryset
What is the best way to add metadata to the response with data from the get_queryset function ?
I'm looking for something similar to the way pagination works.
{
query : {
terms : ['term1','term2'],
tags : ['tag1','tag2'] ,
}
results : [
{ name : 'item1', .... }
{ name : 'item2', .... }
]
}
EDIT
So i created a custom FilterBackend for the filtering and I now have an instance of the request and the response. Looking at the pagination code for django rest i see it's wrapping the results in serializer. The pagination is build into the view class so the fw invokes the serialization if a paginator is detected. Looking at the search api did not produce any new ideas.
My question remains, What is the best, and least intrusive way, of adding metadata from the filter backend to the response ?
One way i can think of (and one that i don't like) is to overload the matadata onto the request in the filter backend and override finalize_response in the view - without a doubt the worst way to do it.
I'm not sure it's the best way, but I would probably override get to simply intercept the response object and modify response.data however you wish. Something as simple as
from rest_framework import generics
class SomeModelList(generics.ListCreateAPIView):
"""
API endpoint representing a list of some things.
"""
model = SomeModel
serializer_class = SomeModelSerializer
def get(self, request, *args, **kwargs):
response = super(SomeModelList, self).get(request, *args, **kwargs)
# redefine response.data to include original query params
response.data = {
'query': dict(request.QUERY_PARAMS),
'results': response.data
}
return response
If you found yourself repeating this for multiple list views you could keep yourself DRY using a Mixin and include it in your list API classes:
from rest_framework import generics
from rest_framework.mixins import ListModelMixin
class IncludeQueryListMixin(ListModelMixin):
def list(self, request, *args, **kwargs):
response = super(IncludeQueryListMixin, self).list(request, *args, **kwargs)
# redefine response.data to include original query params
response.data = {
'query': dict(request.QUERY_PARAMS),
'results': response.data
}
return response
class SomeModelList(IncludeQueryListMixin, generics.ListCreateAPIView):
"""
API endpoint representing a list of some things.
"""
model = SomeModel
serializer_class = SomeModelSerializer

Best way to process different POST requests in a single Class Based View?

Developing my first application here and im using a class based view(django.views.generic.base.View) to handle requests from a webpage.
On the webpage I have different forms that send out POST requests, for example, there is text posting form, comment form, vote button, etc. and Im checking POST.has_key() to see which form has been posted and processing according to that.
Whats a better way to do it? And is it possible to define method names such as post_text, post_comment etc and configure dispatch() to run the method accordingly?
I would do it like this:
class AwesomeView(View):
def post(self, request, *args, **kwargs):
# This code is basically the same as in dispatch
# only not overriding dispatch ensures the request method check stays in place.
# Implement something here that works out the name of the
# method to call, without the post_ prefix
# or returns a default method name when key is not found.
# For example: key = self.request.POST.get('form_name', 'invalid_request')
# In this example, I expect that value to be in the 'key' variable
handler = getattr(
self, # Lookup the function in this class
"post_{0}".format(key), # Method name
self.post_operation_not_supported # Error response method
)
return handler(request, *args, **kwargs)
def post_comment(self, request, *args, **kwargs):
return HttpResponse("OK") # Just an example response

Using class based views to process information?

I've been experimenting with Django's Class Based Views and am trying to write a simple class based view that processes certain information in request so that the processed information can be used by the "handler" method.
I don't seem to have fully understood what the docs say and am unsure of whether this should be a Mixin, a generic view or something else. I'm thinking of making a class like this:
class MyNewGenericView(View):
redirect_on_error = 'home'
error_message = 'There was an error doing XYZ'
def dispatch(self, request, *args, **kwargs):
try:
self.process_information(request)
# self.process_information2(request)
# self.process_information3(request)
# etc...
except ValueError:
messages.error(request, self.error_message)
return redirect(self.redirect_on_error)
return super(MyNewGenericView, self).dispatch(request, *args, **kwargs)
def process_information(self, request):
# Use get/post information and process it using
# different models, APIs, etc.
self.useful_information1 = 'abc'
self.useful_information2 = 'xyz'
def get_extra_info(self):
# Get some extra information on something
return {'foo':'bar'}
This will allow someone to write a view like:
class MyViewDoesRealWork(MyNewGenericView):
def get(self, request, some_info):
return render(request, 'some_template.html',
{'info':self.useful_information1})
def post(self, request, some_info):
# Store some information, maybe using get_extra_info
return render(request, 'some_template.html',
{'info':self.useful_information1})
Is the above code the right way to go? Is there any simpler/better way of doing this? Will this prevent the above functionalities from being used in another generic view (e.g. a built-in generic view)?
Have a look at this. great example code. http://www.stereoplex.com/blog/get-and-post-handling-in-django-views
It seems I just asked a stupid question.
This can easily be achieved by making a class that processes that information:
class ProcessFooInformation(object):
def __init__(self, request):
self.request = request
#property
def bar(self):
baz = self.request.GET.get('baz', '')
# do something cool to baz and store it in foobar
return foobar
# etc...
Then using old style function views or new class-based views:
def my_view(request):
foo = ProcessFooInformation(request)
# use foo in whatever way and return a response
return render(request, 'foobar.html', {'foo':foo})
I also made this more efficient by using lazy evaluation of properties.
I adapted ideas from the lazy property evaluation recipe and the comments to write a wrapper:
def lazy_prop(func):
def wrap(self, *args, **kwargs):
if not func.__name__ in self.__dict__:
self.__dict__[func.__name__] = func(self, *args, **kwargs)
return self.__dict__[func.__name__]
return property(wrap)
This evaluates the value of the wrapped method only once per instance and uses a stored value on subsequent calls. This is useful if the property evaluates slowly.