pagination not working correctly

Django pagination

Project: enoc_proj

Env(venv):
Python: Python 3.12.4
Django: 5.1
Bootstrap5 5.3.3
HTMX : 0.0.0

Files included below (some partial) are app lvl:
urls.py, views.py, base.html, index.html, turnover-list.html, turnover_row.html

App: turnover_app

Problem: pagination (Note other apps in this same project use pagination and work fine.)

The code for this app is slightly different from other apps that are working with pagination. I’m using a modal form and some jquery listeners and some httpresponse showmessage code as well, etc. The pagination issue I’m having is at a basic lvl and does not include any of the jquery code or modal form yet.

The pagination code I have tested with were both class and function. Same results with both. So I’m sure the issue is elsewhere.

Problem Outline:

Using just basic view code with NO pagination code the apps work fine. When I change the view.py code to include pagination and add the pagination code to html page(s) is when it fails.

With pagination the html page title, search box, “add New Entry” button and table heading displays. No table rows show at all. When using chrome dev tools I see the rows of data are there in the “Preview” tab, just not displaying in my table. (screenshots included)


image

turnover_app/urls.py

# turnover_app/urls.py

from django.contrib import admin
from django.urls import path, include
from django.contrib.auth import views as auth_views
from django.views.generic import TemplateView
from django.conf import settings
from django.conf.urls.static import static
from django.contrib.staticfiles.urls import staticfiles_urlpatterns
from django.urls import reverse
from . import views
from .views import list_turn

app_name = 'turnover_app'

urlpatterns = [
  path('turnover_app/', views.turnover_index, name='turnoverhome'),
]
htmxpatterns = [
    path('create_turn/', views.create_turn, name='create_turn'),
    path('delete_turn/<int:pk>/', views.delete_turn, name='delete_turn'),
    path('mark_turn/<int:pk>/', views.mark_turn, name='mark_turn'),
    path('edit_turn/<int:pk>/', views.edit_turn, name='edit_turn'),                             
    #path('edit_turn_submit/<int:pk>/', views.edit_turn_submit, name='edit_turn_submit'),        
    path('search_results_view/', views.search_results_view, name='search_results_view'),        
    path('setOrderby/', views.setOrderby, name='setOrderby'), 
    
    path('list_turn/', views.list_turn, name='list_turn'),              # use with pagination function
    #path('list_turn/', list_turn.as_view(), name='list_turn'),         # use with pagination class
    
    path('current_datetime/', views.current_datetime, name='current_datetime'),
    
]
urlpatterns += htmxpatterns

turnover_app/views.py

# turnover_app/views.py

from django.shortcuts import render, redirect, get_object_or_404
from django.views.generic import TemplateView, ListView
from django.views.generic.edit import CreateView, UpdateView, DeleteView
from django.http import HttpResponse
from django.views.decorators.http import require_http_methods, require_POST
from django import forms
from django.forms import modelform_factory
from django.urls import reverse_lazy
import htmx, random, requests, json
from django.core.paginator import Paginator, EmptyPage, PageNotAnInteger   # for pagination to work must have this line
from django.contrib.auth.decorators import login_required
from django.template.loader import render_to_string

from .models import ToLog, ToLogChild
from .forms import ToLogForm
import datetime


def turnover_index(request):
    return render(request, 'turnover_app/index.html', {})

'''
# no pagination. Works fine.
def list_turn(request):
    return render(request, 'turnover_app/turnover_row.html', {
        'tologs': ToLog.objects.all().order_by(_ORDBY),
    })
'''

def list_turn(request):
    tologs = ToLog.objects.all()
    paginator = Paginator(tologs, 3)
    page_number = request.GET.get('page')
    page_obj = paginator.get_page(page_number)
    return render(request, template_name='turnover_app/turnover_row.html', context={'page_obj':page_obj})

'''
# class based pagination  from djangoproject.com
class list_turn(ListView):
    model = ToLog
    template_name = 'turnover_app/turnover_row.html'
    context_object_name = 'page'
    paginate_by = 4
'''
 
'''
def list_turn(request):
    tologs = ToLog.objects.all()
    paginated = Paginator(tologs, 3)
    page_number = request.GET.get('page') #Get the requested page number from the URL
    page = paginated.get_page(page_number)
    return render(request, 'turnover_app/turnover_row.html', {'page':page})
'''
'''    
def list_turn(request):
    object_list = ToLog.objects.all().order_by(_ORDBY)
    page_num = request.GET.get('page', 1)
    paginator = Paginator(object_list, 4) # 4 employees per page
    try:
        page_obj = paginator.page(page_num)
    except PageNotAnInteger:
        # if page is not an integer, deliver the first page
        page_obj = paginator.page(1)
    except EmptyPage:
        # if the page is out of range, deliver the last page
        page_obj = paginator.page(paginator.num_pages)

    return render(request, 'turnover_app/turnover_row.html', {'page_obj': page_obj})    
'''

templates/turnover_app/base.html

{# templates/turnover_app/base.html #}

{% load static %}
<!doctype html>
<html lang="en">
  <head>
    <meta charset="utf-8">
    <meta name="viewport" content="width=device-width, initial-scale=1">
    <title>{% block title %}TurnOver Log{% endblock %}</title>
    
    <!-- Bootstrap 5 css -->
    <link rel="stylesheet" href="{% static 'css/bootstrap.min.css' %}">

    <!-- Custom styles -->
    <!--link rel="stylesheet" href="{% static 'css/styles.css' %}"-->
  </head>
  <body>

        <div class="container mt-5">
            {% block content %}
            
            {% endblock %}
        </div>

      <script src="{% static 'js/bootstrap.bundle.min.js' %}"></script>

      <script src="{% static 'js/htmx.min.js' %}"></script>

      <script>
        ; (function () {
          const modal = new bootstrap.Modal(document.getElementById("modal"))
          
          htmx.on("htmx:afterSwap", (e) => {
            // Response targeting #dialog => show the modal
            if (e.detail.target.id == "dialog") {
              modal.show()
            }
          })

          htmx.on("htmx:beforeSwap", (e) => {
            // Empty response targeting #dialog => hide the modal
            if (e.detail.target.id == "dialog" && !e.detail.xhr.response) {
              modal.hide()
              e.detail.shouldSwap = false
              // alert("myEvent was triggered!");   // this actually works. put a little dialog box up at top of screen.
            }
          })
    
          // Remove dialog content after hiding
          htmx.on("hidden.bs.modal", () => {
            document.getElementById("dialog").innerHTML = ""
            
          })
        })()
      </script>
      <script>
          ; (function () {
            const toastElement = document.getElementById("toast")
            const toastBody = document.getElementById("toast-body")
            const toast = new bootstrap.Toast(toastElement, { delay: 2000 })
    
            htmx.on("showMessage", (e) => {
              toastBody.innerText = e.detail.value
              toast.show()
            })
          })()
      </script>

      {% block js_script %}
      {% endblock %}

      <script>
        document.body.addEventListener('htmx:configRequest', (event) => {
          event.detail.headers['X-CSRFToken'] = '{{ csrf_token }}';
        })
      </script>
  </body>
</html>

templates/turnover_app/index.html

{# templates/turnover_app/index.html #}

{% extends 'turnover_app/base.html' %}
{% load static %}                   <!-- new 7-4-2024-->

{% block title %} TurnOver Home {% endblock %}
{% block content %}

<h3 class="my-5">TurnOver Log</h3>
{% csrf_token %}
<!--  this section is for the search box -->
<input class="form-control" type="search"
       name="search" placeholder="Begin Typing To Search for a title..." 
       hx-get="{% url 'turnover_app:search_results_view' %}" 
       hx-trigger="keyup changed delay:500ms"
       hx-target="#turnList">
       
<br>
    <button hx-get="{% url 'turnover_app:create_turn' %}"
    hx-target="#dialog"
    class="btn btn-primary">
      Add New Entry
    </button>

<div id="turnList">
    {% include 'turnover_app/turnover-list.html' %}
</div>

<!-- Modal -->
 <div id="modal" class="modal fade">
  <div id="dialog" class="modal-dialog" hx-target="this"></div>
</div>

<!-- Empty toast to show the message -->
<div class="toast-container position-fixed top-0 end-0 p-3">
  <div id="toast" class="toast align-items-center text-white bg-success border-0" role="alert" aria-live="assertive"
    aria-atomic="true">
    <div class="d-flex">
      <div id="toast-body" class="toast-body"></div>
      <button type="button" class="btn-close btn-close-white me-2 m-auto" data-bs-dismiss="toast"
        aria-label="Close"></button>
    </div>
  </div>
</div>

{% endblock %}

{% block js_script %}

<script>
    document.body.addEventListener('htmx:configRequest', (event) => {
        event.detail.headers['X-CSRFToken'] = '{{ csrf_token }}';
    })
</script>
{% endblock %}

templates/turnover_app/turnover-list.html

{# templates/turnover_app/turnover-list.html #}

<div class="table-responsive" style="height: 500px; overflow-y: auto;">
  <table  class="table table-striped w-100 d-block d-md-table">
    <form>
    <thead>
        <div>
          <input type="hidden" id="txword" name="wordup" value="order_by_title">  <!-- value is sent back to views.py def setOrderby(request): -->
        <th>
          <a class="" href="" 
            hx-headers='{"X-CSRFToken": "{{ csrf_token }}"}'
            hx-post="{% url 'turnover_app:setOrderby' %}"
            hx-trigger="click"
            hx-target="#turnList"
            hx-include="[name='wordup']"
            >Social Title</a>
        </th>
        </div>
            
         
        <th>Completed</th>
        <th>Date & Time</th>
        <th>Type Of Entry</th>
    </thead>
    
    </form>
    <tbody hx-trigger="load, turnListChanged from:body" hx-get="{% url 'turnover_app:list_turn' %}" hx-target="this">
      <tr>
        <td class="spinner-border" role="status">
          <span class="visually-hidden">Loading...</span>
        </td>
      </tr>
    </tbody>
  </table>
</div>

templates/turnover_app/turnover_row.html

{# templates/turnover_app/turnover_row.html #}

{# if page #}
{# for tolog in page #}

{% if page_obj %}
{% for tolog in page_obj %}

  <tr>
    <td style="word-wrap: break-word;min-width: 160px;max-width: 260px;">{{ tolog.social_title }}</td>
    <td>{{ tolog.completed }}</td>
    <td>{{ tolog.date_time }}</td> 
    
    {% if tolog.entrytype == 1 %}
    <td>Control-M</td>
    {% elif tolog.entrytype == 2 %}
    <td>SolarWinds</td>
    {% elif tolog.entrytype == 3 %}
    <td>FYI</td>
    {% elif tolog.entrytype == 4 %}
    <td>Other</td>
    {% elif tolog.entrytype == 5 %}
    <td>Adhoc</td>
    {% else %}
    <td>Not Set Yet</td>
    {% endif %}
    <td>
      <button type="button" 
      class="btn-sm btn btn-outline-danger" 
      hx-get="{# url 'turnover_app:delete_turn' tolog.pk #}" 
      hx-target="#turnList" 
      hx-confirm="Are you sure you wish to delete?" 
      style="cursor: pointer;">
      DEL
      </button>
      <button 
      class="btn btn-primary btn-sm" 
      hx-get="{% url 'turnover_app:edit_turn' pk=tolog.pk %}" 
      hx-target="#dialog">
      Edit
      </button>
    </td>
  </tr>
{% endfor %}    


<!—Below is code used from latest Djangoproject.com 

<div class="pagination">
    <span class="step-links">
        {% if page_obj.has_previous %}
            <a href="?page=1">&laquo; first</a>
            <a href="?page={{ page_obj.previous_page_number }}">previous</a>
        {% endif %}

        <span class="current">
            Page {{ page_obj.number }} of {{ page_obj.paginator.num_pages }}.
        </span>

        {% if page_obj.has_next %}
            <a href="?page={{ page_obj.next_page_number }}">next</a>
            <a href="?page={{ page_obj.paginator.num_pages }}">last &raquo;</a>
        {% endif %}
    </span>
</div>

<!—below is used for class based pagination and some function(s)  see below for function based pagination code used -->
<br>

<div class="btn-group" role="group" aria-label="Item pagination">
  {% if page_obj.has_previous %}
      <a href="?page={{ page_obj.previous_page_number }}" class="btn btn-outline-primary">&laquo;</a>
  {% endif %}

  {% for page_number in page_obj.paginator.page_range %}
      {% if page_obj.number == page_number %}
          <button class="btn btn-outline-primary active">
              <span>{{ page_number }} <span class="sr-only">(current)</span></span>
          </button>
      {% else %}
          <a href="?page={{ page_number }}" class="btn btn-outline-primary">
              {{ page_number }}
          </a>
      {% endif %}
  {% endfor %}

  {% if page_obj.has_next %}
      <a href="?page={{ page_obj.next_page_number }}" class="btn btn-outline-primary">&raquo;</a>
  {% endif %}
</div>


{% else %}

<h5>Currently, you don't have any turnOver entries.</h5>

{% endif %}


# function based pagination code
<div class="btn-group" role="group" aria-label="Item pagination">
{% if page.has_previous %}
        <a href="?page={{page.previous_page_number}}" 
 class="btn btn-secondary mx-2">Previous</a>
    {% endif %}

    <a href="?page=1" class="btn btn-secondary">First</a>

    {% for num in page.paginator.page_range %}
        {% if num == page.number %}
            <span>{{ num }}</span>
        {% else %}
            <a href="?page={{num}}" class="btn btn-secondary mx-2">
 {{ num }}
 </a>
        {% endif %}
    {% endfor %}

    <a href="?page={{page.paginator.num_pages}}" class="btn btn-secondary mx-2">
 Last
 </a>

    {% if page.has_next %}
        <a href="?page={{page.next_page_number}}" class="btn btn-secondary mx-2">
 Next
 </a>
    {% endif %}
</div>

First of all I suggest installing django-fastdev so that errors in your templates become errors and not silent failures. I would be this is your issue.

Thank you for the advice.
I’m very interested in iommi .

Can you use bootstrap 5 modal forms with iommi?
How do I use bootstrap 5 like the rest of my project / apps are using?
Can / will it work automatically with one to many models?
Does iommi work with HTMX ?
Where else can I put the iommi settings at besides in settings.py? My apps will need to be removable from this django project and installed in another django project.

Do u have examples of above?

Please advise
Thank you
John

Modal forms? iommi is bootstrap5 styled by default. But you can configure it yourself or use another CSS framework. I don’t understand how “modal” and “forms” combine in this question.

Yes, reverse foreign keys are supported by the auto machinery.

There is no special support for htmx, but since iommi is traditional html from the server, it should combine cleanly. I know some people have used some htmx with it. I personally don’t.

Where else can I put the iommi settings at besides in settings.py? My apps will need to be removable from this django project and installed in another django project.

That’s a bit vague. Which iommi settings specifically? There aren’t a lot of them… and some of them a reusable app really shouldn’t touch I think.

Wow, never thought i would put up an issue in the forum and no one can address the issue.

I recommend patience. Please remember that everyone here are volunteers, answering questions as time, knowledge, and energy provides, and that 4 days - especially in front of and over a holiday weekend - is not a long time.

Honestly, I think you are more likely going to attract attention sooner if you trimmed this down to a minimal reproducable situation.

Yes, totally understand. Thank you.
Its pretty much trimmed way down to minimal code to reproduce. I have read many of your replies always asking for more code so i thought this was good.
This very issue is 1 i have seen others fight with and no answers I’m aware of.
I can make pagination work fine but at the expense of the modal jquery listener.
I can get the modal listener code working fine but at the expense of pagination working.
Have seen others struggle with this. I’m aware that there are a multitude of smarter coders than i and can figure this out.
When asking for help I should have more patience as you suggested. Thank you. Happy hoilday.