class CoursesListView(View):
template_name = 'courses/courses_list.html'
queryset = CoursersModels.objects.all()
# def get_queryset(self):
# return self.queryset
def get(self,request, *args, **kwargs):
queryset = CoursersModels.objects.all()
context = {
#"object_list": self.queryset #If self.queryset is called, the page will not be updated after refresh
"object_list": queryset#Refresh normally after calling
}
return render(request,self.template_name,context)
I am very confused about this code,When I insert a piece of data into the database,If "object_list": self.queryset is called, the page will not be updated after refresh,You must restart django to update the display normally,but when I run "object_list": queryset everything is normal . Thanks in advance.
If "object_list": self.queryset is called, the page will not be updated after refresh.
Yes, that is because the get_queryset() in Django's ListView does not directly return the queryset, it will return self.queryset.all(). Indeed, if we look at the source code [GitHub]:
def get_queryset(self):
# …
if self.queryset is not None:
queryset = self.queryset
if isinstance(queryset, QuerySet):
queryset = queryset.all() # ← makes a clone
elif self.model is not None:
queryset = self.model._default_manager.all()
else:
# …
# …
It thus returns a self.queryset.all() and this is a QuerySet that has not been loaded (yet), and thus will, when enumerating over it, make a new database query.
You thus can implement this with:
class CoursesListView(View):
template_name = 'courses/courses_list.html'
queryset = CoursersModels.objects.all()
def get(self,request, *args, **kwargs):
context = {
'object_list': self.queryset.all()
}
return render(request,self.template_name,context)
That being said, instead of starting with a "simple" View, you can make use of Django's ListView:
from django.views.generic import ListView
# work with a ListView ↓
class CoursesListView(ListView):
template_name = 'courses/courses_list.html'
model = CoursersModels
Related
I have my custom API view and I want to use Search filter in this view, but generic filter dodn't work so i want to create custom one but It doesn't work and I dont know where is problem.
view
class TaskIndexAPIView(APIView):
filter_backends = (CustomSearchFilter,)
search_fields = ('name', 'description', 'user__username')
def get_queryset(self):
return Task.objects.all()
def get(self, request):
tasks = self.get_queryset()
for i in tasks:
if i.date <= date.today():
i.delayed = 'This task is delayed'
i.save()
else:
i.delayed = ''
i.save()
serializer = IndexSerializer(tasks, many=True)
return Response(serializer.data)
My custom search filter
search_filter
class CustomSearchFilter(filters.SearchFilter):
def get_search_fields(self, view, request):
if request.get_queryset.get('name', 'user'):
return ['name', 'user']
return super(CustomSearchFilter, self).get_search_fields(view, request)
In your context, the difference between APIView and generic view is, the generic view has a method called filter_queryset() which handles the filtering and searching operations.
So here in your view, we need to include the same.
class TaskIndexAPIView(APIView):
filter_backends = (CustomSearchFilter,)
search_fields = ('name', 'description', 'user__username')
def filter_queryset(self, queryset):
"""
Given a queryset, filter it with whichever filter backend is in use.
You are unlikely to want to override this method, although you may need
to call it either from a list view, or from a custom `get_object`
method if you want to apply the configured filtering backend to the
default queryset.
"""
for backend in list(self.filter_backends):
queryset = backend().filter_queryset(self.request, queryset, self)
return queryset
def get_queryset(self):
return Task.objects.all()
def get(self, request):
the_filtered_qs = self.filter_queryset(self.get_queryset())
tasks = the_filtered_qs
for i in tasks:
if i.date <= date.today():
i.delayed = 'This task is delayed'
i.save()
else:
i.delayed = ''
i.save()
serializer = IndexSerializer(tasks, many=True)
return Response(serializer.data)
you will get the filtered queryset as
the_filtered_qs = self.filter_queryset(self.get_queryset())
Using Django Class Based Views, I already have a ListView for Order objects, and I created a FormView to perform advanced searches on these orderers.
However, I'm not sure how to pass the filtered queryset of the FormView to the ListView.
Here is the code, with commented sections to explain the issue:
class OrdersListView(PermissionRequiredCanHandleOrders,
SelectRelatedMixin, PrefetchRelatedMixin,
ModelInContextMixin, SubSectionLastOrders,
RestaurantOrdersOnly,
ListView):
model = Order
paginator_class = DiggPaginator
paginate_by = 15
select_related = ('convive__user',)
prefetch_related = ('orderoperation_set',)
# will use the template named order_list.html
class OrdersAdvancedSearchView(PermissionRequiredCanHandleOrders,
ModelInContextMixin, SubSectionLastOrders,
RestaurantOrdersOnly, RestaurantMixin,
FormView):
model = Order
template_name = "orders/order_advanced_search.html"
form_class = OrderAdvancedSearchForm
def form_valid(self, form):
data = form.cleaned_data
queryset = Order.objects.all()
# Here, I'm using the form content to filter the queryset
# queryset = queryset.filter(some_attribute__in=data['stuff'])
# MY PAIN POINT IS HERE: what I'm supposed to do here ?
# my queryset is filtered, and I need to call OrderListView
# with it.
return super().form_valid(form)
You should use the ListView to filter the queryset with the GET request parameters. For this, add the form to your context and process it when getting the queryset:
def get_queryset(self):
self.form = OrderAdvancedSearchForm(data=self.request.GET or None)
if self.request.GET and form.is_valid():
# filter using the form's cleaned_data
queryset = super().get_queryset().filter(...)
else:
queryset = super().get_queryset()
return queryset
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs) # this calls self.get_queryset() which assigns self.form
context['form'] = self.form
return context
Now in your template you can just render the same form, except its method should be "GET" not "POST".
I have a APIView class for showing all the rents and posting and delete etc. Now i want search feature so i tried to use DjangoFilterBackend but it is not working. I see in documentation, it has been used with ListAPIView but how can i use it in APIView.
class Rent(APIView):
"""
List all the rents if token is not provided else a token specific rent
"""
serializer_class = RentSerializer
filter_backends = (DjangoFilterBackend,)
filter_fields = ('city', 'place', 'property_category',)
search_fields = ('=city', '=place')
def get(self, request, token=None, format=None):
reply={}
try:
rents = Rental.objects.all()
if token:
rent = Rental.objects.get(token=token)
reply['data'] = self.serializer_class(rent).data
else:
reply['data'] = self.serializer_class(rents, many=True).data
except Rental.DoesNotExist:
return error.RequestedResourceNotFound().as_response()
except:
return error.UnknownError().as_response()
else:
return Response(reply, status.HTTP_200_OK)
when i search the rent with the following parameters in the url, i get all the rents, instead i should get only those rents that lies in city Kathmandu and place koteshwor
http://localhost:8000/api/v1/rents?city=Kathmandu&place=Koteshwor
To use the functionality of DjangoFilterBackend, you could incorporate the filter_queryset method from GenericViewSet, which is the DRF class that inherits from APIView and leads to all specific 'generic' view classes in DRF. It looks like this:
def filter_queryset(self, queryset):
"""
Given a queryset, filter it with whichever filter backend is in use.
You are unlikely to want to override this method, although you may need
to call it either from a list view, or from a custom `get_object`
method if you want to apply the configured filtering backend to the
default queryset.
"""
for backend in list(self.filter_backends):
queryset = backend().filter_queryset(self.request, queryset, self)
return queryset
https://github.com/encode/django-rest-framework/blob/master/rest_framework/generics.py
Here If you are using APIView, There is nothing to do with filters.So you have to do like
get_data = request.query_params #or request.GET check both
Then
Rental.objects.filter(city=get_data['city'], place=get_data['place'])
In case someone is wondering how can we integrate django_filters filter_class with api_views:
#api_view(['GET'])
#permission_classes([permissions.IsAuthenticated])
def filter_data(request, format=None):
qs = models.YourModal.objects.all()
filtered_data = filters.YourFilter(request.GET, queryset=qs)
filtered_qs = filtered_data.qs
....
return response.Ok(yourData)
Adding to #ChidG's answer. All you need to do is override the DjangoFilterBackend's filter_queryset method, which is the entry point for the filter, and pass it the instance of your APIView. The important point to note here is you must declare filter_fields or filter_class on the view in order to get the filter to work. Otherwise it just return your queryset unfiltered.
If you're more curious about how this works, the class is located at django_filters.rest_framework.backends.py
In this example, the url would look something like {base_url}/foo?is_active=true
from django_filters.rest_framework import DjangoFilterBackend
class FooFilter(DjangoFilterBackend):
def filter_queryset(self, request, queryset, view):
filter_class = self.get_filter_class(view, queryset)
if filter_class:
return filter_class(request.query_params, queryset=queryset, request=request).qs
return queryset
class Foo(APIView):
permission_classes = (AllowAny,)
filter_fields = ('name', 'is_active')
def get(self, request, format=None):
queryset = Foo.objects.all()
ff = FooFilter()
filtered_queryset = ff.filter_queryset(request, queryset, self)
if filtered_queryset.exists():
serializer = FooSerializer(queryset, many=True)
return Response(serializer.data, status=status.HTTP_200_OK)
else:
return Response([], status=status.HTTP_200_OK)
class ProfileContextMixin(generic_base.ContextMixin, generic_view.View):
def get_context_data(self, **kwargs):
context = super(ProfileContextMixin, self).get_context_data(**kwargs)
profile = get_object_or_404(Profile, user__username=self.request.user)
context['profile'] = profile
return context
class CourseListView(ProfileContextMixin, generic_view.ListView):
model = Course
template_name = 'course_list.html'
object_list = None
def get_queryset(self):
profile = self.get_context_data()['profile']
return super(CourseListView, self).get_queryset().filter(creator=profile)
I have the following two class-based-views. CourseListView inherits ProfileContextMixin which I wrote so that I don't have to repeat overriding get_context_data to get the profile every time in my other views.
Now in my CourseListView, I need to filter the result based on the creator argument, which is the same one retrieved in get_context_data
I know my get_queryset works, and it will call get_context_data() to get the profile, but this will also cause my get_context_data to be called twice, executing the same SQL two times.
Is there a way I can access the context efficiently?
UPDATE:
After reading ListView method flowchart, I ended up doing this, but not sure if it's the best way. Feedback is appreciated.
object_list = []
context = None
def get_context_data(self, **kwargs):
return self.context
def get_queryset(self):
self.context = super(CourseListView, self).get_context_data()
profile = self.context['profile']
queryset = super(CourseListView, self).get_queryset()
queryset = queryset.filter(creator=profile)
self.context['object_list'] = queryset
return queryset
You can move getting profile out of get_context_data to upper function, like dispatch, or use cached_property decorator. This way your profile will be stored in _profile argument of view and you will not do second get to DB after calling self.profile second time.
from django.utils.functional import cached_property
class ProfileContextMixin(generic_base.ContextMixin, generic_view.View):
#cached_property
def profile(self):
return get_object_or_404(Profile, user__username=self.request.user)
def get_context_data(self, **kwargs):
context = super(ProfileContextMixin, self).get_context_data(**kwargs)
context['profile'] = self.profile
return context
class CourseListView(ProfileContextMixin, generic_view.ListView):
model = Course
template_name = 'course_list.html'
object_list = None
def get_queryset(self):
return super(CourseListView, self).get_queryset().filter(creator=self.profile)
In Django Rest Framework want to pass a kwarg to my ClubFilter class from within my view.
class ClubView(ListCreateView):
queryset = Club.objects.all()
serializer_class = ClubSerializer
filter_backends = (DjangoFilterBackend,)
filter_class = ClubFilter
def list(self, request, *args, **kwargs):
queryset = self.filter_queryset(self.get_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)
So I tried added self.filter_class = ClubFilter(**kwargs) to the list methoud but I get the error:
'ClubFilter' object is not callable
How can I pass in kwarg to my filter from the view?
filter_class must be an class, it can't be class instance. So you must pass that variable when filter object is created.
Creating of filter object is done in filter backend, so you should subclass your filter backend and provide in it some method that can take your kwargs.
Im assuming that you're using DjangoFilterBackend. You can then subclass it like this:
class MyFilterBackend(DjangoFilterBackend):
def filter_queryset(self, request, queryset, view):
filter_class = self.get_filter_class(view, queryset)o
if filter_class:
if hasattr(view, 'get_filter_kwargs'):
filter_kwargs = view.get_filter_kwargs(queryset=queryset)
else:
filter_kwargs = {'queryset': queryset}
return filter_class(request.query_params, **filter_kwargs).qs
return None
That will allow you to create method get_filter_kwargs inside your view, that should return all kwargs that shuld be passed into your filter class. Remember to pass also queryset that is provided in kwarg for that method.
You can create custom filter backend
filters.py
class MyFilterBackend(DjangoFilterBackend):
def filter_queryset(self, request, queryset, view):
filter_class = self.get_filter_class(view, queryset)
if filter_class:
return filter_class(request.query_params, queryset=queryset, **kwargs).qs
return queryset
and use it instead DjangoFilterBackend