Django pagination in TemplateView? - django

Django pagination in TemplateView?
Hello, I am trying to figure out how I can access multiple models in a Class View and paginate at the same time.
My Outcome after reading, DjangoDoc and Stackoverflow:
ListView - I simply can use paginate_by= but I can load only one Model.
TemplateView - I can load many models but can not use paginate_by=?
For example three Models: Chicken, Cows and Cats (and I want to display on my page the last 3 entries of each model). All Models have a model field called entry date.
class HomeIndex(TemplateView):
template_name = 'home.html'
paginate_by = 3 # --- something like that
def get_context_data(self, **kwargs):
context = super(HomeIndex, self).get_context_data(**kwargs)
context['chickens'] = Chicken.objects.order_by('-entry_date'')
context['cows'] = Cows.objects.order_by('-entry_date'')
context['cats'] = Cats.objects.order_by('-entry_date')
return context
Or maybe I can add something to objects.order_by('-entry_date', < pagination? >).
Thanks

Django QuerySet has built-in results slicing.
Cows.objects.order_by('-entry_date'')[offset:limit]
For the last 3 entries, offset is 0 and the limit 3
Cows.objects.order_by('-entry_date'')[0:3]
or the same can be written in a more pythonic way
Cows.objects.order_by('-entry_date'')[:3]
To get last 3 cows, cats and chicken, following code will work.
def get_context_data(self, **kwargs):
context = super(HomeIndex, self).get_context_data(**kwargs)
context['chickens'] = Chicken.objects.order_by('-entry_date')[:3]
context['cows'] = Cows.objects.order_by('-entry_date')[:3]
context['cats'] = Cats.objects.order_by('-entry_date')[:3]
return context
References:
Limiting Queries link

If you want to use ListView you still can, by chaining the querysets in get_queryset(self) and paginating that (read this answer to see the chain and sorted explained). This way you can use the default simple pagination.
from itertools import chain
from operator import attrgetter
class HomeIndex(ListView):
template_name = 'home.html'
paginate_by = 3 # --- somehting like that
def get_queryset(self):
chicken_list = Chicken.objects.all()
cow_list = Cows.objects.all()
cat_list = Cats.objects.all()
result_list = sorted(
chain(chicken_list, cow_list, cat_list),
key=attrgetter('entry_date'),
reverse=True)
return result_list
Then in your template:
{% for data in object_list %}
{{ data }}
{% endfor %}
And you can use the pagination as shown here.

Related

How to add extra context data in LISTVIEW without breaking built-in pagination

Question about pagination issue:
Why if I have for example following LISTVIEW:
class BoatListView(ListView):
model = BoatModel
template_name = "boats.html"
paginate_by = 5
def get_context_data(self, *, object_list=None, **kwargs):
context = ListView.get_context_data(self, object_list=None, **kwargs)
context["boats"] = BoatModel.objects.all()
context["images"] = BoatImage.objects.all()
return context
and I would use “boats” and “images” context in template , for example :
{% for boat in boats %}
some code here
{% endfor %}
...
…
….
{% bootstrap_pagination page_obj %}
paginator will not work at all in this case ( bootstrap one or original Django https://docs.djangoproject.com/en/2.2/topics/pagination/#using-paginator-in-a-view), no difference?
But as soon as I change “boats” and “images” to “object_list” - paginator would begin pagination.
Whats the problem and how in this case could I add extra context in view if I need to do so within ability to use paiginator indeed?
Thank you!
ListView declares an attribute object_list which takes the queryset from get_queryset(). When constructing the context, this attribute is used to define pagination. You can override the behaviour of the pagination in get_context_data itself by changing what is sent as a queryset in self.paginate_queryset(queryset, page_size)(I don't see a reason to do this though).
Take a look at how ListView works here.

Django: How can I reverse objects with DetailView?

I have used generic views.Every Episode is connected to a certain season through ForeignKey. In views.py I have these:
class SeasonList(generic.ListView):
template_name = 'episodes/episodes.html'
context_object_name = 'all_seasons'
def get_queryset(self):
return reversed(Season.objects.all())
# Here I need to sort the episodes
class SeasonDetails(generic.DetailView):
model = Season
template_name = 'episodes/season_details.html'
In the list view I used reversed() to show the latest season first. Similarly, In the detail view, I want the episodes to appear in the descending order because the latest episode should appear at the top of the page.
In my html I have accessed the episodes list using season.episode_set.all
{% for episode in season.episode_set.all %}
<!-- the tags to show the list -->
{% endfor %}
Is there any way how I can reverse the episode list?
You could order by id and use descendant - or ascendant depending on your need
Season.objects.all().order("id") # Ascendant
Season.objects.all().order("-id") # Decendant
Or reverse() will be ok to reverse the queryset whatever your filter.
Season.objects.all().reverse()
To sort the episodes, you can use something like this -
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
context['episodes'] = Episodes.objects.all().order_by('-date_posted')
return context

Pagination from a Django DetailView

Given a models.py:
class Partner(models.Model):
... (fields irrelevant to this example)
class Lecture(models.Model):
... (other fields not relevant to this example)
partner models.ForeignKey(Partner)
I have a ListView for each and a DetailView for each (working fine).
The problem is that on the Partner DetailView page, I have the list of Lectures. In many cases this list can be quite long (e.g., >200), and the stakeholders want it to be paginated. I've had no problems with pagination on the ListView pages (that's easy), but I can't seem to figure out on the Partner's DetailView page how to paginate on their Lecture list.
I was hoping to see in the Django docs code that would look something like:
class PartnerDetailView(DetailView):
model = Partner
paginate_using = Lecture # (or something like self.lecture ?)
paginate_by = 20
whereupon the DetailView would act on the single Partner object, but would (easily) allow pagination from the Lecture FK results.
Is there support for this? Or will it require far more custom view code (perhaps putting a 'page' variable in **kwargs for get_context_data() and creating the subset based on that)?
It just seems like a very common situation under CBVs so I'm puzzled why a search hasn't turned up any examples.
UPDATE: It should have occurred to me that a simple way to do this would be to just add a "page" piece to the url() entry that references the DetailView and use that to create the subset of pagination on the FK objects.
Note that this also might be a workable approach to getting around the "how do you paginate results from a FormView" issue...
Your best bet is probably to subclass ListView rather than DetailView, and override get_queryset to get the Lectures from the Partner. You can add the Partner object in get_context_data as well if you need it.
MultipleObjectMixin with DetailView
Hello. I would use MultipleObjectMixin and have pagination just like you did in ListView.
from django.views.generic.detail import DetailView
from django.views.generic.list import MultipleObjectMixin
class PartnerDetailView(DetailView,MultipleObjectMixin):
model = Partner
paginate_by = 5
def get_context_data(self, **kwargs):
object_list = Lecture.objects.filter(partner=self.get_object())
context = super(PartnerDetailView, self).get_context_data(object_list=object_list, **kwargs)
return context
Now in your template you can use object_list (all the lectures that are related to this partener) and use pagination.
{% if is_paginated %}
{% include "includes/pagination.html" %}
{% endif %}
where in "includes/pagination.html" you have access to the pagination context(page_obj).
To extend #MsCheikh answer i will provide version without MultipleObjectMixin. Sample of my
class DealDetailView(DetailView):
model = Deal
template_name = "crm/deals/deal_detail.html"
def get_context_data(self, **kwargs):
context = super(DealDetailView, self).get_context_data(**kwargs)
activities= self.get_related_activities()
context['related_activities'] = activities
context['page_obj'] = activities
return context
def get_related_activities(self):
queryset = self.object.activity_rel.all()
paginator = Paginator(queryset,5) #paginate_by
page = self.request.GET.get('page')
activities = paginator.get_page(page)
return activities
So later i include standard Django pagination template (copied from docs) and iterate over related_activities

Django: Search form in Class Based ListView

I am trying to realize a Class Based ListView which displays a selection of a table set. If the site is requested the first time, the dataset should be displayed. I would prefer a POST submission, but GET is also fine.
That is a problem, which was easy to handle with function based views, however with class based views I have a hard time to get my head around.
My problem is that I get a various number of error, which are caused by my limited understanding of the classed based views. I have read various documentations and I understand views for direct query requests, but as soon as I would like to add a form to the query statement, I run into different error. For the code below, I receive an ValueError: Cannot use None as a query value.
What would be the best practise work flow for a class based ListView depending on form entries (otherwise selecting the whole database)?
This is my sample code:
models.py
class Profile(models.Model):
name = models.CharField(_('Name'), max_length=255)
def __unicode__(self):
return '%name' % {'name': self.name}
#staticmethod
def get_queryset(params):
date_created = params.get('date_created')
keyword = params.get('keyword')
qset = Q(pk__gt = 0)
if keyword:
qset &= Q(title__icontains = keyword)
if date_created:
qset &= Q(date_created__gte = date_created)
return qset
forms.py
class ProfileSearchForm(forms.Form):
name = forms.CharField(required=False)
views.py
class ProfileList(ListView):
model = Profile
form_class = ProfileSearchForm
context_object_name = 'profiles'
template_name = 'pages/profile/list_profiles.html'
profiles = []
def post(self, request, *args, **kwargs):
self.show_results = False
self.object_list = self.get_queryset()
form = form_class(self.request.POST or None)
if form.is_valid():
self.show_results = True
self.profiles = Profile.objects.filter(name__icontains=form.cleaned_data['name'])
else:
self.profiles = Profile.objects.all()
return self.render_to_response(self.get_context_data(object_list=self.object_list, form=form))
def get_context_data(self, **kwargs):
context = super(ProfileList, self).get_context_data(**kwargs)
if not self.profiles:
self.profiles = Profile.objects.all()
context.update({
'profiles': self.profiles
})
return context
Below I added the FBV which does the job. How can I translate this functionality into a CBV?
It seems to be so simple in function based views, but not in class based views.
def list_profiles(request):
form_class = ProfileSearchForm
model = Profile
template_name = 'pages/profile/list_profiles.html'
paginate_by = 10
form = form_class(request.POST or None)
if form.is_valid():
profile_list = model.objects.filter(name__icontains=form.cleaned_data['name'])
else:
profile_list = model.objects.all()
paginator = Paginator(profile_list, 10) # Show 10 contacts per page
page = request.GET.get('page')
try:
profiles = paginator.page(page)
except PageNotAnInteger:
profiles = paginator.page(1)
except EmptyPage:
profiles = paginator.page(paginator.num_pages)
return render_to_response(template_name,
{'form': form, 'profiles': suppliers,},
context_instance=RequestContext(request))
I think your goal is trying to filter queryset based on form submission, if so, by using GET :
class ProfileSearchView(ListView)
template_name = '/your/template.html'
model = Person
def get_queryset(self):
name = self.kwargs.get('name', '')
object_list = self.model.objects.all()
if name:
object_list = object_list.filter(name__icontains=name)
return object_list
Then all you need to do is write a get method to render template and context.
Maybe not the best approach. By using the code above, you no need define a Django form.
Here's how it works : Class based views separates its way to render template, to process form and so on. Like, get handles GET response, post handles POST response, get_queryset and get_object is self explanatory, and so on. The easy way to know what's method available, fire up a shell and type :
from django.views.generic import ListView if you want to know about ListView
and then type dir(ListView). There you can see all the method defined and go visit the source code to understand it. The get_queryset method used to get a queryset. Why not just define it like this, it works too :
class FooView(ListView):
template_name = 'foo.html'
queryset = Photo.objects.all() # or anything
We can do it like above, but we can't do dynamic filtering by using that approach. By using get_queryset we can do dynamic filtering, using any data/value/information we have, it means we also can use name parameter that is sent by GET, and it's available on kwargs, or in this case, on self.kwargs["some_key"] where some_key is any parameter you specified
Well, I think that leaving validation to form is nice idea. Maybe not worth it in this particular case, because it is very simple form - but for sure with more complicated one (and maybe yours will grow also), so I would do something like:
class ProfileList(ListView):
model = Profile
form_class = ProfileSearchForm
context_object_name = 'profiles'
template_name = 'pages/profile/list_profiles.html'
profiles = []
def get_queryset(self):
form = self.form_class(self.request.GET)
if form.is_valid():
return Profile.objects.filter(name__icontains=form.cleaned_data['name'])
return Profile.objects.all()
This is similar to #jasisz 's approach, but simpler.
class ProfileList(ListView):
template_name = 'your_template.html'
model = Profile
def get_queryset(self):
query = self.request.GET.get('q')
if query:
object_list = self.model.objects.filter(name__icontains=query)
else:
object_list = self.model.objects.none()
return object_list
Then all you have to do on the html template is:
<form method='GET'>
<input type='text' name='q' value='{{ request.GET.q }}'>
<input class="button" type='submit' value="Search Profile">
</form>
This has been explained nicely on the generic views topic here Dynamic filtering.
You can do filtering through GET, I don't think you can use POST method for this as ListView is not inherited from edit mixings.
What you can do is:
urls.py
urlpatterns = patterns('',
(r'^search/(\w+)/$', ProfileSearchListView.as_view()),
)
views.py
class ProfileSearchListView(ListView):
model = Profile
context_object_name = 'profiles'
template_name = 'pages/profile/list_profiles.html'
profiles = []
def get_queryset(self):
if len(self.args) > 0:
return Profile.objects.filter(name__icontains=self.args[0])
else:
return Profile.objects.filter()
I think that the error you are getting is because your form doesn't require the name field. So, although the form is valid, the cleaned_data for your name field is empty.
These could be the problematic lines:
if form.is_valid():
self.show_results = True
self.profiles = Profile.objects.filter(name__icontains=form.cleaned_data['name'])
If I were you, I would try changing the line:
self.profiles = Profile.objects.filter(name__icontains=form.cleaned_data['name'])
to this:
self.profiles = Profile.objects.none()
If you stop receiving errors (and your template receives an empty object_list), the problem you have is what I said before: name field not required.
Let us know if this doesn't work!
Search on all fields in model
class SearchListView(ItemsListView):
# Display a Model List page filtered by the search query.
def get_queryset(self):
fields = [m.name for m in super(SearchListView, self).model._meta.fields]
result = super(SearchListView, self).get_queryset()
query = self.request.GET.get('q')
if query:
result = result.filter(
reduce(lambda x, y: x | Q(**{"{}__icontains".format(y): query}), fields, Q())
)
return result
def get_queryset(self):
query_name = self.request.GET.get('query', '')
object_list = Product.objects.filter(
Q(title__icontains=query_name)
)
return object_list
<form action="" method="GET">
{% csrf_token %}
<input type="text" name="query" placeholder="Search keyword">
<i class="ti-search"></i>
</form>

In Django, using a generic list class, how can I get N of an object_list from the same field type

I have a like model that collects likes that users select on books.
So, each record has the user_id, like_id and book_id.
I want a url that is something like:
(?P<top_num>\d+)/likes/
Wich would be directed to a view that does something like this:
class TopLikes(ListView):
""" Get all the archived projects """
queryset = Like.objects.filter(display=True)
template_name = "books/TopLikes.html"
paginate_by = 10
#method_decorator(login_required)
def dispatch(self, *args, **kwargs):
return super(TopLikes, self).dispatch(*args, **kwargs)
What I don't know is how to take that top_num as the number to pass to the view to return the top ten books with the most likes.
it would make sense to me to do something like queryset = Like.objects.filter(display=True).annotate(num_books=Count('books')).order_by('num_books')
It makes sense to me to get the likes and then use the likes to do something like this in the template:
{% for object in object_list %}
{{ object.book.title }} with {{ object|length }}
{% endfor %}
Would this just be easier to do as a custom view?
Thanks
Override get_queryset() method, so that you can add custom filtering
Use self.kwargs, so that you can use top_num url parameter to limit your queryset
Use {{ object.num_books }}, because well what is {{ object|length }} supposed to do anyway :)
Example:
class TopLikes(ListView):
""" Get all the archived projects """
queryset = Like.objects.filter(display=True)
template_name = "books/TopLikes.html"
paginate_by = 10
def get_queryset(self):
qs = super(TopLikes, self).get_queryset()
qs = qs.annotate(num_books=Count('books')).order_by('num_books')
qs = qs[:self.kwargs['top_num']]
return qs