Django Diaries - Pagination
Tags: webdev, django
Published: 2020-08-09 13:32:05

This is part 4 of the Django Diaries, we will look at implementing Pagination in Django.

The code from Corey Schafer's Tutorial can be found [here](https://github.com/CoreyMSchafer/code_snippets/tree/master/Django_Blog) (Number 11). **This blog series is entirely based on what I learned from here:**

{% youtube acOktTcTVEQ %}

Django has pagination sorted for us, we just need to implement the buttons and set some attributes.

## Example
For this example I will open the django shell with `python manage.py shell`
```python
>>> from django.core.paginator import Paginator
>>> posts = ['1','2','3','4','5','6','7','8','9','10']
# Posts is our object of posts and 2 is the number of posts per page
>>> p = Paginator(posts,2)
# Return the number of pages in this case we have 10 post with 2
# per page, so 10/2
>>> p.num_pages
5
# page_range is an object of pages
>>> for page in p.page_range:
...     print(page)
1
2
3
4
5
# Get specific page
>>> p1 = p.page(1)
>>> p1
<Page 1 of 5>
# get page number
>>> p1.number
1
# get all the posts on that page
>>> p1.object_list
['1','2']
# Check for previous and next pages
>>> p1.has_previous()
False
>>> p1.next_page_number()
2
``` 
## Creating a Paginator
For paginating our blog post app, we need to specify some attributes in the view. 
```python
class PostListView(ListView):
    model = Post
    template_name = "blog/home.html" 
    context_object_name = 'posts'
    ordering = ['-date_posted']
    # Add Django Pagination
    paginate_by = 4
```
Now the Paginator knows now many posts should be on each page, simply by adding a 'paginate_by' attribute, because we are using a class based view we don't actually need to import paginator. That's the pagination done. I set 'paginate_by' to four as I want 4 posts per page. 

If we reload the server and visit '/?page=2' we should see the next page being loaded. All we need to do now is to add links to the next page in our templates.

## Adding buttons to the template
For this I will use a Bootstrap button group.

![Alt Text](https://dev-to-uploads.s3.amazonaws.com/i/2jdiaz37c4pe8jwbybh2.jpg)

Here we check if there is a Paginator active. Then we add the First and Previous Page links. We Check if there is a previous page, and then set a link to page 1 by setting the page to 1. We can then access the page object using `page_obj` to get the previous page number and link to that.
 
![Alt Text](https://dev-to-uploads.s3.amazonaws.com/i/eknt3oajn13wbehgaihb.jpg)

We do the same to add buttons for the next and last page. Notice we are setting the last page number to the number of pages; because if we have 25 pages then the last page will be number 25.

Finally we loop over the number of pages to create a link for a couple of pages around the current page. So if we are on page six we have links to 7,8,9 and 5,4,3 just so we don't have all the numbers showing.

![Alt Text](https://dev-to-uploads.s3.amazonaws.com/i/c1u945o3gdly4hg8tbc8.jpg)

We create a for loop to loop over all the pages. Then we add a button for the current page with different styles, using a condition to check if the loop value is the current page. We then print a few pages around the current page using django filters, so we check if the current number is > 3 pages bellow the current page and < 3 pages after. (This all goes in the is paginated conditional)

![Alt Text](https://dev-to-uploads.s3.amazonaws.com/i/pth67jmdbrx1xpwm1g1o.png)

Here you can see that other pages are displayed around the current page, I only have 3 pages so we don't see 3 up and 3 down.

## Adding links for user specific posts

Now we would like to see all of one users posts when we click on their name. 

In order to modify the results of a query from a view we can define a method called `get_queryset()`

For this I will create a new view.
```python
from django.shortcuts import get_object_or_404
from django.contrib.auth.models import User

class UserPostListView(ListView):
    model = Post
    template_name = "blog/user_post.html" 
    context_object_name = 'posts'  
    paginate_by = 4

    # over ride query
    def get_queryset(self):
        user = get_object_or_404(User, username=self.kwargs.get("username"))
        return Post.objects.filter(author=user).order_by('-date_posted')
```  
We have created a new list view, and defined a get_queryset method. Now we can use a django shortcut called get_object_or_404 to make user the user exists. We get the username from the url using `self.kwargs.get('username')`. Kwargs refers to the keyword arguments from the url. We then filter the posts be author and reorder then because we are overiding the query.
Finally add this to `urls.py`
```python
 path("user/<str:username>", UserPostListView.as_view(), name="user-posts")
```
Last but not least we need to make a `user_post` template, as that's what we called it in the view. For this we can just copy the home template as it will be very similar. I will add a conditional on top to display the users name or 'Your Posts' of the user is looking at their own posts.
![Alt Text](https://dev-to-uploads.s3.amazonaws.com/i/i5ghb1210hcqgezrhxr3.jpg)

We will also add links in all templates to go to the users posts.
![Alt Text](https://dev-to-uploads.s3.amazonaws.com/i/jn65fpwb2innuz6s2wac.jpg)

Thank you for reading part 4. Up next is the finally part where we will send emails to reset a users password. 

DISCLAIMER: This is all from Corey Schafters Tutorial series, I you want to build this project I highly recommend you check out the [videos](https://www.youtube.com/playlist?list=PL-osiE80TeTtoQCKZ03TU5fNfx2UY6U4p) If you want to build this yourself.

-----

✨If you would like to follow my day to day development journey be sure to check my [Instagram](https://www.instagram.com/me_richard1/).