changeing ListView get_queryset? pagination in django - python

Good day everyone.
I am having some trouble with the paginator in django.
I have in my db all the customers info, so what I want to do is to display that info, but I made a search function in which you clic a letter button, for example you clic A and it will filter all the customers that their last name starts with the letter A, if you clic B , it will filter all customers with their last name start with B, and so on. This works correctly, the problem is that I also want to display 10 customers per page, so if I have 20 customers that their last name starts with the letter A, what you will see, will be 10 customers and a bar that says ( <<< page 1 page 2 >>> ) or something like that, and that should be solved with the paginator, I added it, but its not working.
I think the problem is that maybe my get function is rewriting the get_query function from ListView perhaps? I tryed different things but I'm not sure.
Here is my code in views:
class ExpedientView(ListView):
queryset = Portfolio.objects.filter(products__isnull=True).order_by('owner__last_name')
template_name = 'dashboard-admin/portfoliorecords.html'
paginate_by = 10
def get(self,request):
if request.GET['letter'] == '':
context_object_name = 'portfolios'
queryset = Portfolio.objects.filter(products__isnull=True).order_by('owner__last_name')
context = queryset
else:
letter = request.GET['letter']
context_object_name = 'portfolios'
queryset = Portfolio.objects.filter(products__isnull=True).order_by('owner__last_name').filter(owner__last_name__istartswith=letter)
context = queryset
return render(request, 'dashboard-admin/portfoliorecords.html', {'portfolios': context})
the get(self,request) function, works perfectly, however the first part where the paginated_by is not working.
Before I added the get function, and just filtering all the customers, the paginator worked fine, so in the template, the code works properly.

You have to modify the def get_queryset(self) method not the def get(self, request) method
Remove the def get(self, request) method and the below code.
def get_queryset(self):
queryset = Portfolio.objects.filter(products__isnull=True).order_by('owner__last_name')
letter = request.GET.get('letter', None)
if letter:
queryset.filter(owner__last_name__istartswith=letter)
return queryset

If all you need to do is dynamically change the queryset then instead of overriding get (Which calls the appropriate functions in the view, which will perform all pagination tasks, etc.) you should be overriding get_queryset:
class ExpedientView(ListView):
queryset = Portfolio.objects.filter(products__isnull=True)
template_name = 'dashboard-admin/portfoliorecords.html'
paginate_by = 10
ordering = 'owner__last_name' # Put the ordering here instead of specifying it in the queryset
def get_queryset(self):
queryset = super().get_queryset()
letter = self.request.GET.get('letter')
if letter:
queryset = queryset.filter(owner__last_name__istartswith=letter)
return queryset

Related

How to remove items from queryset based on condition and then return as json response in django

I am doing join operation using select_related() and filtering records using below code
class ActiveclientViewSet(viewsets.ModelViewSet):
queryset = Ruledefinitions.objects.select_related('pmdclinicalruleid').filter(pmdclinicalruleid__effectivedate__lt = timezone.now(),pmdclinicalruleid__retireddate__gt = timezone.now())
serializer_class = RuledefinitionsSerializer
In the above code, is it possible to check whether the first item from queryset has rulename field value as empty and if it is empty i need to return remaining queryset items in json response if not empty return all items as json response.
What's wrong with checking the first element ?
class ActiveclientViewSet(viewsets.ModelViewSet):
queryset = Ruledefinitions.objects.select_related('pmdclinicalruleid')
serializer_class = RuledefinitionsSerializer
def get_queryset(self):
now = timezone.now
queryset = super().get_queryset().filter(
pmdclinicalruleid__effectivedate__lt=now,
pmdclinicalruleid__retireddate__gt=now,
)
first_item = queryset.first()
if first_item is not None and not first_item.rulename:
queryset = queryset[1:]
return queryset
Your filter on timezone.now() is only executed once: when your classe is defined. So any call to this method should not be in the classe definition, but called every request.
In your actual implementation, now would be called as soon as you start the server. Two weeks later, the filter would still be at the same date.

Getting two database models to show on one html page

Currently working on a web page and I want to show two different tables of information one is for individual foods and the other is for recipes, however I can only get the first class to pull up any information I've tested to see if the first class can pull up the recipe database and it, in fact, currently out of ideas on what to try next.
class SearchFoodResultsView(ListView):
model = Food
template_name = 'nutrihacker/search.html'
context_object_name = 'food_list'
# overrides ListView get_queryset to find names containing search term and pass them to template
def get_queryset(self):
query = self.request.GET.get('term')
if (query == None):
return Food.objects.all()
else: # If there are any foods containing the query, they will be in the resulting object_list which is used by search.html in a for loop
food_list = Food.objects.filter(
Q(name__icontains = query)
)
return food_list
class SearchRecipeResultsView(ListView):
model = RecipePreset
template_name = 'nutrihacker/search.html'
context_object_name = 'recipe_list'
# overrides ListView get_queryset to find names containing search term and pass them to template
def get_queryset(self):
query = self.request.GET.get('term')
if (query == None):
return RecipePreset.objects.all()
else: # If there are any recipes containing the query, they will be in the resulting object_list which is used by search.html in a for loop
recipe_list = RecipePreset.objects.filter(
Q(name__icontains = query)
)
return recipe_list

Django - Add object in front of existing QuerySet

I am building a library Django application where I have a BooksView to display all Books written by a certain Author. Under certain conditions, I want a single Book to be displayed before all other books in the query set. This should happen when the id of the book appears in the URL, otherwise, all books should be listed in chronological order.
books/urls.py:
urlpatterns = [
# Books displayed in chronological order
path("<slug:author>/books", views.BooksView.as_view()),
# Books displayed in chronological order
# but first book is the one with id `book`
path("<slug:author>/books/<int:book>", views.BooksView.as_view()),
]
books/views.py:
class BooksView(APIView):
def get(self, request, author, book=None):
author = get_object_or_404(Author, slug=author)
books = author.author_books
books = books.order_by("-pub_date")
if book:
book = get_object_or_404(Book, id=book)
# TODO: add `book` in front of books QuerySet (and remove duplicated if any)
serializer = BooksSerializer(books, many=True)
return Response(serializer.data)
How can I accomplish my TODO? How can I push an object in front of an existing query set?
This doesn't precisely answer your question but I think it would allow you to get the desired outcome: don't worry about preserving the queryset and instead transform it into a list.
This isn't a thing you want to do for every queryset in your entire codebase especially if it's a queryset that you might later want to build upon and compose with other filters, excludes, etc. But when you're about to render a page (like you are here) it is OK because you're about to cause the queryset to be evaluated anyhow. You're causing it to happen perhaps microseconds sooner.
class BooksView(APIView):
def get(self, request, author, book=None):
author = get_object_or_404(Author, slug=author)
books = list(author.author_books)
if book:
book = Book.objects.get(id=book)
# TODO: add `book` in front of books QuerySet (and remove duplicated if any)
books = [x for x in books if not x == book] # first remove if dup
books.insert(0, book) # now insert at front
serializer = BooksSerializer(books, many=True)
return Response(serializer.data)
EDIT 1
The BooksSerializer (which subclasses the BaseSerializer I suspect) is going to make a list anyhow as soon as you call it:
def to_representation(self, data):
"""
List of object instances -> List of dicts of primitive datatypes.
"""
# Dealing with nested relationships, data can be a Manager,
# so, first get a queryset from the Manager if needed
iterable = data.all() if isinstance(data, models.Manager) else data
return [
self.child.to_representation(item) for item in iterable
]
https://github.com/encode/django-rest-framework/blob/master/rest_framework/serializers.py#L663
EDIT 2
What about trying this instead? By adding the exclude before the queryset is evaluated into a list you prevent the O(n) scan through the list to find and remove the "main" book that's supposed to be at the top.
class BooksView(APIView):
def get(self, request, author, book=None):
author = get_object_or_404(Author, slug=author)
books = author.author_books
if book:
book = Book.objects.get(id=book)
# TODO: add `book` in front of books QuerySet (and remove duplicated if any)
# ensure the queryset doesn't include the "main" book
books = books.exclude(book_id=book.id)
# evaluate it into a list just like the BookSerializer will anyhow
books = list(books)
# now insert the "main" book at the front of the list
books.insert(0, book)
serializer = BooksSerializer(books, many=True)
return Response(serializer.data)
You can use the bitwise | operator as
from django.http.response import Http404
class BooksView(APIView):
def get(self, request, author, book=None):
author = get_object_or_404(Author, slug=author)
book_qs = author.author_books
if book:
single_book_qs = Book.objects.filter(id=book)
if not single_book_qs.exists():
raise Http404
book_qs = single_book_qs | book_qs
serializer = BooksSerializer(book_qs, many=True)
return Response(serializer.data)
Please note that one caveat of this solution is that, if you use the order_by(...) method, the position will be changed according to the ORDER By expression.
Update 1
Since you are using an order_by(...) expression, you must do something like this,
class BooksView(APIView):
def get(self, request, author, book=None):
author = get_object_or_404(Author, slug=author)
book_qs = author.author_books.order_by("-pub_date")
serialized_single_book = []
if book:
single_book = get_object_or_404(Book, id=book)
book_qs.exclude(id=book) # to remove dups
serialized_single_book = [BooksSerializer(single_book).data]
serializer = BooksSerializer(book_qs, many=True)
serialized_book_qs = serializer.data
return Response([*serialized_single_book, *serialized_book_qs])
This should do it, just use the union method in the QuerySet object, and exclude
the book we are trying to access from books queryset.
I did some minor changes to the code, but you don't need to do more than that, to accomplish what you need.
class BooksView(APIView):
def get(self, request, author, book=None):
author = get_object_or_404(Author, slug=author)
books = author.author_books.all().order_by('-pub_date')
if book:
# I am assuming this line if for validating that the request is valid.
# I've changed the query to also include author's slug,
# so the request doesn't get books not related to the author.
book_obj = get_object_or_404(Book, id=book, author__slug=author)
# Just Query the same book, and union it with the books queryset,
# excluding the current book in the book_obj
books = Book.objects.filter(id=book_obj.id).union(books.exclude(id=book_obj.id))
serializer = BooksSerializer(books, many=True)
return Response(serializer.data)
A little out of the box maybe, but if you want to keep it as queryset, then this is the solution. Please note that this solves the X problem, not the Y problem. The goal is to put one book first in the list, which can be achieved by insertion, but also by reordering based on an annotation.
from django.db.models import Case, When, Value, IntegerField
from rest_framework import generics
from . import models, serializers
class BooksPerAuthor(generics.ListAPIView):
serializer_class = serializers.BookWithoutAuthor
def get_queryset(self):
book_pk = self.kwargs.get("book", 0)
queryset = (
models.Book.objects.filter(author__slug=self.kwargs["author"])
.annotate(
promoted=Case(
When(pk=book_pk, then=Value(1)),
default=Value(0),
output_field=IntegerField(),
)
)
.order_by("-promoted", "-pub_date")
)
return queryset
This doesn't catch non-existing references - not a fan of returning 404 for list views and in case the promoted book is not in the collection, then there's no real harm done. Plenty of examples above to do the same thing with 404's at the (minor) cost of additional queries.

Apply sort using python(not QuerySet) to models using ListView pagination

Apply sort using python(not QuerySet) to models using ListView pagination
I use Django ListView with pagination feature to display models.
Since I need to sort list of models using method field(need to send param to method thus it must be a method) return value, I cannot complete sorting using get_query_set method.
Since get_context_data is called for each page, I cannot add sort to there.
class SpotRankingView(ListView):
model = Spot
template_name = 'places/spot_list.html'
paginate_by = 5
def get_context_data(self, **kwargs):
context = super(SpotRankingView, self).get_context_data(**kwargs)
# This is called for every page
# So I cannot add sort here
return context
def get_queryset(self):
# I need method return value to sort models
#
# ex. I need something like blow to sort models
# method category as blow is a calculated property on model.
# spot.category(category_name = 'Relaxing').points
#
# Those cannot be done by QuerySet so I caanot add that
# sorting here
return Spot.objects.all()
How can I sort models using python list sorting & make them paginate?
Not sure what kind of sorting you need. There are a lot of sorting methods like by date, by id, by reverse date. Since you are using Class Based Views you can use ArchiveIndexView like below.
from django.views.generic.dates import ArchiveIndexView
class SpotRankingView(ArchiveIndexView):
model = Spot
template_name = 'places/spot_list.html'
paginate_by = 5
date_field = "date_created"
You need to set allow_future to True if you want to show objects with date in future. If you still don't understand then follow the reference.
You also can do this for your list view like:
class SpotRankingView(ListView):
model = Spot
template_name = 'places/spot_list.html'
paginate_by = 5
def get_ordering(self):
ordering = self.request.GET.get('ordering', '-date_created')

How to sort by many custom methods in Django Admin

I want to be able to sort by several custom methods in Django Admin. This question provides solution for one method only.
I tried to modify it:
from django.db import models
class CustomerAdmin(admin.ModelAdmin):
list_display = ('number_of_orders','number_of_somevalue') # added field
def queryset(self, request):
qs = super(CustomerAdmin, self).queryset(request)
qs = qs.annotate(models.Count('order'))
qs = qs.annotate(models.Count('somevalue')) # added line
return qs
def number_of_orders(self, obj):
return obj.order__count
number_of_orders.admin_order_field = 'order__count'
def number_of_somevalue(self, obj): # added method
return obj.somevalue__count
number_of_somevalue.admin_order_field = 'somevalue__count'
and it works incorrectly. It seems that it multiplies the count values instead of counting them separately.
Example:
I have 2 orders and 2 somevalues, but in the panel I see 4 orders and 4 somevalues.
Adding another method with yet another value makes it 8 (2*2*2).
How can I fix it?
You can try this to sort by many custom methods (Tested):
from django.db.models import Count
class CustomerAdmin(admin.ModelAdmin):
# The list display must contain the functions that calculate values
list_display = ('number_of_orders','number_of_somevalue') # added field
# Overwrite queryset in model admin
def queryset(self, request):
qs = super(CustomerAdmin, self).queryset(request)
# The query have to return multiple annotation, for this use distinct=True in the Count function
qs = qs.annotate(number_orders = Count('order', distinct=True)).annotate(number_somevalue = Count('somevalue',distinct=True))
return qs
# This function return the new field calculated in queryset (number_orders)
def number_of_orders(self, obj):
return obj.number_orders
number_of_orders.admin_order_field = 'numberorders' # sortable new column
# And this one will return the another field calculated (number_somevalue)
def number_of_somevalue(self, obj): # added method
return obj.number_somevalue
number_of_somevalue.admin_order_field = 'number_somevalue'# sortable new column

Categories

Resources