Pagination from a Django DetailView - django

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

Related

Django: how to re-create my function-based view as a (Generic Editing) Class Based View

I have a function-based view that is currently working successfully. However, I want to learn how to create the equivalent Class Based View version of this function, using the generic UpdateView class -- though I imagine the solution, whatever it is, will be the exact same for CreateView, as well.
I know how to create and use Class Based Views generally, but there is one line of my function-based view that I have not been able to work into the corresponding UpdateView -- as usual with the Generic Editing Class Based Views, it's not immediately clear which method I need to override to insert the desired functionality.
The specific task that I can't port-over to the CBV, so to speak, is a line that overrides the queryset that will be used for the display of a specific field, one that is defined as ForeignKey to another model in my database.
First, the working function-based view, with highlight at the specific bit of code I can't get working in the CVB version:
#login_required
def update_details(request, pk):
"""update details of an existing record"""
umd_object = UserMovieDetail.objects.select_related('movie').get(pk=pk)
movie = umd_object.movie
if umd_object.user != request.user:
raise Http404
if request.method != 'POST':
form = UserMovieDetailForm(instance=umd_object)
# this is the single line of code I can't get working in UpdateView version:
form.fields['user_guess'].queryset = User.objects.filter(related_game_rounds=movie.game_round)
else:
form = UserMovieDetailForm(instance=umd_object, data=request.POST)
if form.is_valid():
form.save()
return redirect(movie)
context = {'form': form, 'object': umd_object }
return render(request, 'movies/update_details.html', context)
I can recreate every part of this function-based view in UpdateView successfully except for this line (copied from above for clarity):
form.fields['user_guess'].queryset = User.objects.filter(related_game_rounds=movie.game_round)
What this line does: the default Form-field for a ForeignKey is ModelChoiceField, and it by default displays all objects of the related Model. My code above overrides that behavior, and says: I only want the form to display this filtered set of objects. It works fine, as is, so long as I'm using this function-based view.
Side-Note: I am aware that this result can be achieved by modifying the ModelForm itself in my forms.py file. The purpose of this question is to better understand how to work with the built-in Generic Class Based Views, enabling them to recreate the functionality I can already achieve with function-based views. So please, refrain from answering my question with "why don't you just do this in the form itself instead" -- I am already aware of this option, and it's not what I'm attempting to solve, specifically.
Now for the UpdateView (and again, I think it would be the same for CreateView). To start off, it would look essentially like this:
class UpdateDetailsView(LoginRequiredMixin, UpdateView):
model = UserMovieDetail
template_name = 'movies/update_details.html'
form_class = UserMovieDetailForm
login_url = 'login' # used by LoginRequiredMixin
# what method do I override here, to include that specific line of code, which needs
# to occur in the GET portion of the view?
def get_success_url(self):
return reverse('movies:movie', kwargs={'pk': self.object.movie.pk, 'slug': self.object.movie.slug })
The above is a working re-creation of my function-based view, replicating all the behavior except that one important line that filters the results of a specific field's ModelChoiceField display in the Form.
How do I get that line of code to function inside this UpdateView? I've reviewed the methods built-in to UpdateView on the classy class-based views website, and then attempted (by pure guess-work) to over-ride the get_form_class method, but I it didn't work, and I was basically shooting in the dark to begin with.
Note that since the functionality I want to re-create is about the display of items in ModelChoiceField of the form, the desired behavior applies to the GET portion of the view, rather than the POST. So I need to be able to override the form fields before the form is rendered for the first time, just like I did in my function based view. Where and how can I do this in UpdateView?
First, a note not related to form - from raise Http404 in functional view I understand that you want to allow user to access only his own movies. For that in class based view you can override get_queryset method:
class UpdateDetailsView(LoginRequiredMixin, UpdateView):
def get_queryset(self):
return UserMovieDetail.objects \
.filter(user=request.user) \
.select_related('movie')
Now let's move to customizing form.
Option 1 - .get_form()
You can override get_form method of the UpdateView:
class UpdateDetailsView(LoginRequiredMixin, UpdateView):
form_class = UserMovieDetailForm
def get_form(self, form_class=None):
form = super().get_form(form_class)
# add your customizations here
round = self.object.movie.game_round
form.fields['user_guess'].queryset = \
User.objects.filter(related_game_rounds=round)
return form
Option 2 - moving customizations to form class and .get_form_kwargs()
You might prefer to move customization logic from view to form. For that you can override form's __init__ method. If customization logic requires extra information (for example, queryset depends on current user), then you can also override get_form_kwargs method to pass extra parameters to the form:
# views.py
class UpdateDetailsView(LoginRequiredMixin, UpdateView):
form_class = UserMovieDetailForm
def get_form_kwargs(self):
kwargs = super().get_form_kwargs()
kwargs.update({'current_user': self.request.user})
return kwargs
# forms.py
class UserMovieDetailForm(forms.ModelForm):
def __init__(self, *args, **kwargs):
self.current_user = kwargs.pop('current_user')
super().__init__(*args, **kwargs)
# add your customizations here
self.fields['user_guess'].queryset = ...
P.S. In general, great resource for understanding django class based views is https://ccbv.co.uk/

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 pagination in TemplateView?

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.

Access django celery results in views

I'm using the django-celery-results extension and successfully saving records in the db backend table, celery_results_taskresults. My tasks are associated with model instances, and I want to be able to list them as attributes of each instance, in views and ultimately templates. I can see them in the admin interface, but can't figure out how to access them in a list.
I though of creating an #property on the model in question, using raw sql, but the sql examples I've seen all refer to a model, and if there's a celery_results_taskresults model, I can't find it.
As celery_results_taskresults use a model to store results, so we can use them in the views. You can try like this:
from django_celery_results.models import TaskResult
class SomeTemplateView(TemplateView):
def get_context_data(self, *args, **kwargs):
context = super(SomeTemplateView, self).get_context_data(*args, **kwargs)
context['results'] = TaskResult.objects.all()
return context
And in the template:
{% for r in results %}
{{r.task_name}}
...
{% endfor %}

How do i display inlines with DetailView?

I have a Project model.
This model has Days which are inlines.
How do I display them using a DetailView?
My views.py looks like this:
class ProjectDetailView(DetailView):
queryset = Project.objects.all()
slug_field = 'slug'
template_name = 'projects/detail_project.html'
How do I pull through the Day inlines with this?
I've tried:
def get_context_data(self, **kwargs):
context = super(ProjectDetailView, self).get_context_data(**kwargs)
project = Project.objects.filter(slug=self.slug_field)
context['days'] = Day.objects.filter(project=project)
return context
But this doesn't work. Also it seems pointless that I'm using a Generic view but then doing a get_object_or_404 anyway to pull the Days out.
How do I do this properly?
There's no such thing as an inline model. There are inline forms, which are forms for a model which has a ForeignKey relationship with a parent model - but you don't seem to be talking about forms.
In any case, there's no need to do anything in code. You can refer to the related models directly in the template:
{% for day in object.day_set.all %}
{{ day.whatever }}
{% endfor %}