Using Pagination with JsonResponse

Hello,

I am new to the forum and currently working on CS50W Network project which is basically a twitter clone.

I want to get different sets of posts via a fetch API request, which was working fine until I implemented Pagination. Now I get a 404 not found.

for example, when the DOM of my index page is loaded I call the getPosts function passing in the string ‘all’ as the username parameter and currentPage as the page parameter.

document.addEventListener('DOMContentLoaded', function() {
   
  // By default, getPosts
  getPosts('all', currentPage);
});
    
    let currentPage = 1;
    const itemsPerPage = 10;
    
    function getPosts(username, page) {
      // Make a GET request to the API endpoint
      fetch(`/get_post/${username}/?page=${page}&per_page=${itemsPerPage}`)
        .then(response => response.json())
        .then(data => displayPosts(data.data))
        .catch(error => console.error(error));
      }

the path for the API is:

path("get_post/<str:username>", views.get_post, name="get_post"),

My view function to get the requested posts and handle the pagination:

@login_required
def get_post(request, username):

    if username == "all":
    # get all posts
        posts = Post.objects.all()

    else:
        # get users posts
        user = User.objects.get(username=username)
        posts = Post.objects.filter(user=user)

    # change button color
    for post in posts:
        if post.likes.filter(id=request.user.id).exists():
            post.liked = True
            post.save()
        else:
            post.liked = False
            post.save()

    # order posts in reverse chronological order
    posts = posts.order_by("-timestamp").all()

    # Get the current page number and the number of items per page from the query parameters
    page_number = int(request.GET.get('page', 1))
    items_per_page = int(request.GET.get('per_page', 10))
    
    # Paginate the posts based on the page number and the number of items per page
    paginator = Paginator(posts, items_per_page)
    page_obj = paginator.get_page(page_number)

    # Create a list of serialized posts for the current page
    serialized_posts = [post.serialize() for post in page_obj]

    # Return a JSON response that includes the serialized posts and pagination metadata
    return JsonResponse({
        'data': serialized_posts,
        'meta': {
            'page': page_obj.number,
            'per_page': items_per_page,
            'total_pages': paginator.num_pages,
            'total_items': paginator.count
        }
    })

the received data gets handed over to the displayPosts function:

function getPosts(username, page) {
  // Make a GET request to the API endpoint
  fetch(`/get_post/${username}/?page=${page}&per_page=${itemsPerPage}`)
    .then(response => response.json())
    .then(data => displayPosts(data.data))
    .catch(error => console.error(error));
  }

the displayPosts function is supposed to append the Posts to the DOM:

function displayPosts(posts) {

    // create a div element for each post
    posts.forEach(post => {
        let div = document.createElement('div');
        div.className = "card";
        div.innerHTML = `
        <div class="card-body">
          <a class="card-title" id="user-link" href="profile/${post['username']}"><strong><h6>${post['username']}</h6></strong></a>
          <h7 class="card-subtitle mb-2 text-muted">${post['timestamp']}</h7>
          <p class="card-text">${post['text']}</p>
          <button class="card-text like-button" pk="${post['id']}" onclick="like_post(${post.id});"><h3> ♥ </button> </h3>
          <span class="like-count" pk="${post['id']}">${post['likes']}</span>
        </div> 
        `;
        // append div to posts-view
        document.querySelector('#posts-view').append(div);
        // get the button
        button = document.querySelectorAll('button[pk="'+post['id']+'"]')[0];
        // change button color
        if (post.liked == true){
          button.classList.add('liked')
        }
        else
          button.classList.remove('liked')
    });
}

In the browser console I get a 404 not found, pointing to the fetch.

I am not sure why. I checked the django docs page Pagination | Django documentation | Django
but in the docs they just return render the page_obj.
and as you can see, I have not added the page navigation buttons yet.

I found the mistake

I forgot the slash after str:username in the path, its supposed to be:

path("get_post/<str:username>/", views.get_post, name="get_post"),

and I also removed the int() for the page_number and items_per_page.

now its working fine. It’s always the same, right after I Post a question in a forum I find the mistake right away… :man_facepalming:

Great to know that you found the solution.
Additionally, I will advise you to take a look into this specific piece of code. This is doing a lot of queries to the database, you probably can refactor this to use less the database. This can kill the performance of your website / database.

1 Like

Thanks alot for the tip!
Looking into this, it seems that I can just remove the section completely. I already set liked in my like_post function, when clicking the like button.

@csrf_exempt
@login_required
def like_post(request, post_id):

# get the Post
post = Post.objects.filter(pk=post_id).first()
if post is None:
    return JsonResponse({"error": "Post not found."}, status=404)

# Return post contents
if request.method == "GET":
    return JsonResponse(post.serialize())


# Update likes
elif request.method == "PUT":
    data = json.loads(request.body)
    if data.get("like") is not None and data.get("like") is True:
        if post.likes.filter(id=request.user.id).exists():
            post.likes.remove(request.user)
            post.liked = False
            post.save()
        else:
            post.likes.add(request.user)
            post.liked = True 
            post.save() 
        # Post's pk, the total number of likes
        return JsonResponse({'pk':post.pk, 'like_count':post.likes.all().count(), 'liked':post.liked})

# Post must be via GET or PUT
else:
    return JsonResponse({
        "error": "GET or PUT request required."
    }, status=400)

ok I was a little confused, this is obviousely not working if someone else loggs in …

still trying to fix this.

This is getting a bit off topic, but I still wanted to show how I handled the like button color change without all the database queries in the view function.

first I removed the ‘liked’ field of my ‘Post’ model.

then I created a list of dictionaries representing the users who liked the post called likes_list. Each dictionary includes the id and username fields of the user. I returned it in my serializer function as likes.

class Post(models.Model):
text = models.TextField(max_length=512)
timestamp = models.DateTimeField(auto_now_add=True)
user = models.ForeignKey(User, on_delete=models.CASCADE, null=True)
likes = models.ManyToManyField(User, related_name=“post_likes”, blank=True)

class Meta:
    ordering = ['timestamp']
        
def total_likes(self):
    return self.likes.count()

def serialize(self):

    likes_list = []
    for user in self.likes.all():
        likes_list.append({
            "id": user.id,
            "username": user.username,
        })

    return {
        "id": self.id,
        "text": self.text,
        "timestamp": self.timestamp.strftime("%b %d %Y, %I:%M %p"),
        "username": self.user.username,
        "total_likes": self.total_likes(),
        "likes": likes_list,
    }

Now the likes_list is returned in the JsonResponse of my ‘get_post’ view function.
therefore I can now access the id and Username of everyone that liked the post.

so I changed the condition to change button color inside the ‘displayPosts’ Javascript function to:

    if (post.likes.some(like => like.id == request_user_id)){
      button.classList.add('liked')
    }
    else
      button.classList.remove('liked')

Would this be a better way to handle it?

1 Like