List pagination in Django - django

I want to paginate a list of books. The api end point is like this /books?page=num&size=num
So the number of page and how many books to load will be variables.
My response should look like:
{
pagesnum= totalpages
booksnum=totalbooks
books=
[ {detailsofbook1},
{...},
{...}
]
}
My code:
urlpattern:
path('api/books?page=<int:page>&size=<int:size>/',BookView.as_view()),
views.py
class BookView(APIView):
http_method_names = ['get']
permission_classes = (permissions.IsAuthenticated,)
def index(request):
books = Books.objects.all()
books_per_page= request.GET.get('size')
book_paginator = Paginator(books, books_per_page)
page_num = request.GET.get('page')
page = book_paginator.get_page(page_num)
context = {
'count': book_paginator.count(),
'page': page
queryset =Books.ojects.all()
}
return JsonResponse(context, status=200)
This implementation doesn't work. First of all something is wrong probably with the url it doesnt seem to understand the variables. Or is something else wrong with my code?

page and size are query parameters, so you get them by accessing request.query_params so it should be like
books_per_page= request.query_params.get('size') # or request.query_params['size]
page_num = request.query_params.get('page')
also, I'm a bit confused with index() I think your code should be in get(self, request, *args, **kwargs)
and my last thing is that you can make use of pagination in DRF, see

Related

How Do I Override GET in Django Detailview?

I have a FORMVIEW that is redirecting to a DETAILVIEW and that is working perfectly. The issue I'm having is when I try to combine Pagination with DetailView. When I try to leverage the pagination, the GET is essentially redirecting me to the FORMVIEW. I get why it's doing this...I'm telling it to. What I'm trying to work out is how I can put in some logic in my overridden GET. I tried to do a self.GET_OBJECT.pk...to see if I could determine if I'm on the current page and not the FORMVIEW but that didn't work...
Here's my DetailView....
def get_context_data(self, **kwargs):
context = super(SuggestionByNameDetailView, self).get_context_data(**kwargs)
attachments = SuggestionFiles.objects.filter(suggestion=self.object.pk).all()
comment_form = SuggestionCommentForm()
response_form = SuggestionCommentReplyForm()
activities= self.get_related_activities()
context['suggestion_comments'] = activities
context['page_obj'] = activities
context['attachments'] = attachments
context['comment_form'] = comment_form
context['response_form'] = response_form
return context
def get_related_activities(self):
queryset = self.object.suggestion_comments.all()
paginator = Paginator(queryset,5) #paginate_by
page = self.request.GET.get('page')
activities = paginator.get_page(page)
return activities
def get_object(self, queryset=None):
return get_object_or_404(Suggestion, id=self.request.GET.get("dropdown"))
def get(self, request, *args, **kwargs):
dropdown=self.request.GET.get("dropdown")
if dropdown is not None:
if Suggestion.objects.filter(Q(id=self.request.GET.get("dropdown"))).distinct():
self.object = self.get_object()
context = self.get_context_data(object=self.object)
return self.render_to_response(context)
else:
raise Http404
else:
messages.add_message(self.request, messages.INFO, 'Suggestion is required.')
return HttpResponseRedirect(reverse('Suggestions:suggestion_by_name'))
As mentioned I did try to do something like...if DROPDOWN is NONE and pk = self.get_object().pk...to essentially try and determine if I can just bypass the DROPDOWN logic in GET...but the PK is always present...I also tried to do something like self.request.GET.get('pk') but that didn't work either.
When I click to do the pagination I get redirected back to the FORMVIEW. I'm trying to avoid that once I'm on the DETAILVIEW. Thanks in advance for any thoughts on what I can do to resolve this.
So....after a couple of hours....turns out....GET may be an issue...but here's a way around it.....The "canned" django code...for pagination looks something like this...
Next
In my case...I simply needed to get more specific with my URL...so it now looks more like....
Next
Case closed. This one anyway. :).

query string, changes required in urls.py and view

A basic django question, but stumping me.
I have an existing view for an endpoint.
http://localhost:8001/v1/subject - which returns all subjects from the subject model.
I want to serve a new endpoint...
http://localhost:8001/v1/subject?owner_ids=60,61,62
What would be the changes required in the "urls.py" definition and
'''
def query_set
'''
method in views/subject.py,
I have added the method but it's not working as intended.
Here's the urls.py definition, any changes required to accommodate a query string?
router.register(r'subjects', views.SubjectsViewSet)
Should I do something like this?
url(r'^v1/subjects/',
views.SubjectViewSet.as_view({'get': 'list'}))
Also, here's my views/subjects.py file...the logic in def get_queryset may work, but how do I wire the urls.py entry so that the querystring localhost:8001/v1/subjects?owner_ids=60,61,62 is served together with the regular localhost:8001/v1/subjects ?
class SubjectViewSet(Subject.get_viewset()):
pagination_class = SubjectPagination
def get_queryset(self, *args, **kwargs):
owner_id_list =
self.request.GET['owner_ids'].split(',')
owner_id_list_integer = []
for i in owner_id_list:
owner_id_list_integer.append(int(i))
queryset = Subject.objects.all()
if owner_id_list is None:
return None
else:
return queryset.filter(organization__in=owner_id_list)
SubjectUserRoleViewSet = Subject.get_by_user_role_viewset(
SubjectViewSet, GroupRoleMap, Role)
Please, try this way.
def get_queryset(self, *args, **kwargs):
splited = self.request.GET['owner_ids'].split(',')
filtered_nums = list(filter(None, splited))
try:
get_nums = list(map(int, filtered_nums))
except ValueError as exc:
return Subject.objects.none()
else:
return Subject.objects.filter(organization__in=get_nums)
I hope, I do not have a mistake.

Creating Template Links for Pagination in Generic CBV

I'm just starting with Django so I have a lot of questions. I've built a Mixin to create the paginator template links for "next page" and previous page in a GCBV (ListView):
class PageLinksMixin:
page_kwarg = 'page'
def _page_urls(self, page_number):
return "?{pkw}={n}".format(pkw=self.page_kwarg, n=page_number)
def previous_page(self, page):
if page.has_previous():
return self._page_urls(page.previous_page_number())
return None
def next_page(self, page):
if page.has_next():
return self._page_urls(page.next_page_number())
return None
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
page = context.get('page_obj')
if page is not None:
context.update({'previous_page_url': self.previous_page(page),
'next_page_url': self.next_page(page)})
return context
Everything seems to work. But I can simply not see where some of the inputs for functions were created. Especially "page", "page_number".
With a CBV it would be necessary to declare:
page = paginator.page(page_number)
But in the GCBV this is apparently unnecessary which means the attribute is created somewhere and later inherited.
For the ListView there is a function def paginate_queryset but in there page is only declared in a local scope not a global scope. Can somebody explain this to me? I'm really confused and trying to figure this out for a while now.
This is the View:
class TagList(PageLinksMixin, ListView):
template_name = 'organizer/tag_list.html'
paginate_by = 5
model = Tag
The Django generic ListView has a paginator object, which is added to the context through self.get_context_data(). Here's the Django source code:
def get_context_data(self, **kwargs):
"""
Get the context for this view.
"""
queryset = kwargs.pop('object_list', self.object_list)
page_size = self.get_paginate_by(queryset)
context_object_name = self.get_context_object_name(queryset)
if page_size:
paginator, page, queryset, is_paginated = self.paginate_queryset(queryset, page_size)
context = {
'paginator': paginator,
'page_obj': page,
'is_paginated': is_paginated,
'object_list': queryset
}
else:
context = {
'paginator': None,
'page_obj': None,
'is_paginated': False,
'object_list': queryset
}
if context_object_name is not None:
context[context_object_name] = queryset
context.update(kwargs)
return super(MultipleObjectMixin, self).get_context_data(**context)
In your mixin, you first call super().get_context_data(), which populates the context with the paginator and a page object, which you set to page:
page = context.get('page_obj')
Finally you call self.previous_page(page) to fetch the actual URL and add it to the context. Nothing magic.
You could also have fetched the page_number from the kwargs and the page from the paginator instance but since it's already done for you, the page_obj in the context is the easiest way.

Add context to head of ListAPIView

I would like to add {'user': self.request.user} to the header of my ListAPIView.
For example, the JSON response would look like:
[
'user': testing_user,
{
id: 67,
slug: "slugy",
},
]
Is it possible to do this with this view?
class BookArchiveAPIView(generics.ListAPIView):
cache_timeout = 60 * 7
serializer_class = BookSerializer
queryset = Book.objects.all()
I know its too late but I needed same thing and just found on the documentation easily.
You don't need to pass the "request.user" to the view because DRF already gives it to you.
Here is the example:
class show_activities(generics.ListAPIView):
serializer_class = ActivitySerializer
def get_queryset(self):
username = self.request.user.username
return Activity.objects.filter(owner=username)
It's easy to find on documentation but I'm answering for the ones who couldn't see it.

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>