π Django β Guide 16: PDF Viewer (Page-by-Page with Buttons)
This guide walks you through setting up a Django application to load and display PDF files inside a Bootstrap card. You will learn how to prepare the project structure, load a PDF from the media folder, and render it page-by-page using PDF.js, with Previous / Next buttons for navigation.
π― Objectives
By the end of this guide, you will:
- β Set up a Django app to display PDF files
- β Store PDF files inside the media folder
- β Serve media URLs using Django settings
- β Render a page-by-page PDF viewer in a Bootstrap card
- β Include Previous / Next buttons for navigation
π Project Structure
project_folder/
βββ manage.py
βββ core/
β βββ settings.py β Project configuration
β βββ urls.py β Core URL routing
β
βββ apps/
β βββ pdfview/ β App for PDF viewing
β β βββ apps.py
β β βββ views.py β Page-by-page rendering logic
β β βββ urls.py β App URLs
β
βββ media/
β βββ README.pdf β PDF file to display
1οΈβ£ Create the PDF View App
If you havenβt created it yet:
python manage.py startapp pdfview
mv pdfview apps/
Add the app to core/settings.py:
INSTALLED_APPS = [
...,
'apps.pdfview',
]
2οΈβ£ Add Media Settings
In core/settings.py, add:
MEDIA_URL = '/media/'
MEDIA_ROOT = BASE_DIR / 'media'
3οΈβ£ Serve Media in URLs
In core/urls.py, add:
from django.conf import settings
from django.conf.urls.static import static
At the bottom:
urlpatterns += static(settings.MEDIA_URL, document_root=settings.MEDIA_ROOT)
4οΈβ£ App URLs
Create apps/pdfview/urls.py:
from django.urls import path
from . import views
app_name = 'pdfview'
urlpatterns = [
path('', views.index, name='index'),
]
5οΈβ£ Views
In apps/pdfview/views.py:
from django.shortcuts import render
def index(request):
return render(request, 'pdfview/index.html', {
'title': 'PDF Viewer',
'pdf_url': '/media/README.pdf', # PDF location
})
6οΈβ£ Template
Create apps/pdfview/templates/pdfview/index.html:
{% extends 'base.html' %}
{% block title %}{{ title }}{% endblock %}
{% block content %}
<div class="card shadow-lg p-4 mb-3 mx-auto" style="max-width: 900px;">
<h4 class="mb-4 text-center">{{ title }}</h4>
<!-- Navigation -->
<div class="d-flex justify-content-between mb-3">
<button id="prev" class="btn btn-primary btn-sm">Previous</button>
<span>Page: <span id="page_num"></span> / <span id="page_count"></span></span>
<button id="next" class="btn btn-primary btn-sm">Next</button>
</div>
<!-- PDF Canvas -->
<div class="text-center">
<canvas id="pdf_render" class="border w-100"></canvas>
</div>
</div>
<!-- PDF.js CDN -->
<script src="https://cdnjs.cloudflare.com/ajax/libs/pdf.js/3.11.174/pdf.min.js"></script>
<script>
const url = "{{ pdf_url }}";
let pdfDoc = null,
pageNum = 1,
pageIsRendering = false,
pageNumPending = null;
const scale = 1.2;
const canvas = document.querySelector("#pdf_render");
const ctx = canvas.getContext("2d");
// Render the page
const renderPage = num => {
pageIsRendering = true;
pdfDoc.getPage(num).then(page => {
const viewport = page.getViewport({ scale });
canvas.height = viewport.height;
canvas.width = viewport.width;
const renderCtx = { canvasContext: ctx, viewport };
page.render(renderCtx).promise.then(() => {
pageIsRendering = false;
if (pageNumPending !== null) {
renderPage(pageNumPending);
pageNumPending = null;
}
});
document.querySelector("#page_num").textContent = num;
});
};
// Queue render
const queueRenderPage = num => {
if (pageIsRendering) {
pageNumPending = num;
} else {
renderPage(num);
}
};
// Previous / Next buttons
document.querySelector("#prev").addEventListener("click", () => {
if (pageNum <= 1) return;
pageNum--;
queueRenderPage(pageNum);
});
document.querySelector("#next").addEventListener("click", () => {
if (pageNum >= pdfDoc.numPages) return;
pageNum++;
queueRenderPage(pageNum);
});
// Load PDF
pdfjsLib.getDocument(url).promise.then(pdfDoc_ => {
pdfDoc = pdfDoc_;
document.querySelector("#page_count").textContent = pdfDoc.numPages;
renderPage(pageNum);
});
</script>
{% endblock %}
7οΈβ£ Add the URL to the Project
In core/urls.py:
path('pdfview/', include('apps.pdfview.urls')),
π Done!
You now have a page-by-page PDF viewer:
- Loads PDFs from
/media/ - Displays inside a Bootstrap card
- Previous / Next buttons work
- Ready for flip animations or zoom features in future guides