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)
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">« 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 »</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">«</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">»</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>