Django PWA Tutorial Series:
- Introduction
- Add Web app manifest to Django
- Add Service Worker to Django
- Fallback Offline Page in Django
- Caching and Routing (coming soon)
- How to add install button to PWA (coming soon)
- Send Web Push Notification from Django (part 1) (coming soon)
- Send Web Push Notification from Django (part 2) (coming soon)
The source code is available on https://github.com/AccordBox/django-pwa-demo
Objectives
The web app manifest is a JSON file that tells the browser about your Progressive Web App and how it should behave when installed on the user's desktop or mobile device. A typical manifest file includes the app name, the icons the app should use, and the URL that should be opened when the app is launched.
By the end of this chapter, you should be able to:
- Understand what is
Web app manifest
and how to generate app icons using NPM package. - Learn how to use
python-webpack-boilerplate
to make Webpack work with Django.
Create Django Project
Create a new project directory:
$ mkdir django_pwa && cd django_pwa
Then, create and activate a new Python virtual environment:
$ python3.9 -m venv env
$ source env/bin/activate
(env)$
Feel free to swap out virtualenv and Pip for Poetry or Pipenv.
Install Django and start a new project:
(env)$ pip install Django==3.2
(env)$ django-admin.py startproject django_pwa_app .
Below is the project structure
.
├── django_pwa_app
│ ├── __init__.py
│ ├── asgi.py
│ ├── settings.py
│ ├── urls.py
│ └── wsgi.py
└── manage.py
(env)$ python manage.py migrate
(env)$ python manage.py runserver
Now check on http://127.0.0.1:8000/ and you will see Django welcome page, then press Ctrl+C
to terminate the dev server.
Import python-webpack-boilerplate
python-webpack-boilerplate is a project aim to help import Webpack to your Django/Flask project.
Make sure you have a requirements.txt file in the project root:
Django==3.2
python-webpack-boilerplate
(env)$ pip install -r requirements.txt
Add webpack_boilerplate
(brought by python-webpack-boilerplate
) to the INSTALLED_APPS
in django_pwa_app/settings.py
Let's run the Django command to create frontend project from the templates
$ python manage.py webpack_init
# here we use the default frontend slug
project_slug [frontend]:
Now you should see frontend
directory is created.
.
├── db.sqlite3
├── django_pwa_app
│ ├── __init__.py
│ ├── __pycache__
│ │ ├── __init__.cpython-36.pyc
│ │ ├── settings.cpython-36.pyc
│ │ ├── urls.cpython-36.pyc
│ │ └── wsgi.cpython-36.pyc
│ ├── asgi.py
│ ├── settings.py
│ ├── urls.py
│ └── wsgi.py
├── frontend
│ ├── README.md
│ ├── package-lock.json
│ ├── package.json
│ ├── postcss.config.js
│ ├── src
│ │ ├── application
│ │ │ ├── app.js
│ │ │ └── app2.js
│ │ ├── components
│ │ │ └── sidebar.js
│ │ └── styles
│ │ ├── bootstrap.scss
│ │ └── index.scss
│ ├── vendors
│ │ └── images
│ │ ├── sample.jpg
│ │ └── webpack.png
│ └── webpack
│ ├── webpack.common.js
│ ├── webpack.config.dev.js
│ ├── webpack.config.prod.js
│ └── webpack.config.watch.js
└── manage.py
Config frontend project
If you have no nodejs installed, please install it first by using below links
$ node -v
v12.20.0
$ npm -v
6.14.8
$ cd frontend
# install dependency packages
$ npm install
# run webpack in watch mode
$ npm run watch
Add code below to Django settings django_pwa_app/settings.py
import os
STATICFILES_DIRS = [
os.path.join(BASE_DIR, "frontend/build"),
]
WEBPACK_LOADER = {
'MANIFEST_FILE': os.path.join(BASE_DIR, "frontend/build/manifest.json"),
}
- We add the above
frontend/build
toSTATICFILES_DIRS
so Django can find the static assets (img, font and others) - We add
MANIFEST_FILE
location to theWEBPACK_LOADER
so our custom loader can help us load JS and CSS.
Simple Test
Updater django_pwa_app/urls.py
from django.contrib import admin
from django.urls import path
from django.views.generic import TemplateView
urlpatterns = [
path('', TemplateView.as_view(template_name="index.html")), # new
path('admin/', admin.site.urls),
]
$ mkdir django_pwa_app/templates
Update TEMPLATES
in django_pwa_app/settings.py, so Django can know where to find the templates
TEMPLATES = [
{
'BACKEND': 'django.template.backends.django.DjangoTemplates',
'DIRS': ['django_pwa_app/templates'], # new
'APP_DIRS': True,
'OPTIONS': {
'context_processors': [
'django.template.context_processors.debug',
'django.template.context_processors.request',
'django.contrib.auth.context_processors.auth',
'django.contrib.messages.context_processors.messages',
],
},
},
]
Create django_pwa_app/templates/index.html
{% load webpack_loader %}
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8" />
<title>Index</title>
{% stylesheet_pack 'app' %}
</head>
<body>
<div class="bg-light py-5">
<div class="container">
<h1 class="display-3">Hello, world!</h1>
<p>This tutorial tutorial series will teach you how to bring PWA to Django</p>
<p><a class="btn btn-primary btn-lg d-none" href="#" role="button" id="installButton">Install PWA</a></p>
</div>
</div>
{% javascript_pack 'app' %}
</body>
</html>
- We
load webpack_loader
at the top of the template - We use
stylesheet_pack
andjavascript_pack
to load CSS and JS bundle files to Django, both of them come frompython-webpack-boilerplate
# please make sure the `npm run watch` is still running
(env)$ python manage.py runserver
Now you should be able to see Hello, world
on the http://127.0.0.1:8000/
Now the frontend project is working with our Django project seamlessly. We will generate Web app manifest
in the next section.
Generate Web app manifest
There are some ways to generate Web app manifest
and app icon
here.
- One way is to use some online services, For example: https://realfavicongenerator.net/, and then download the files.
- Another way is to use some NPM packages to help us. Frontend ecosystem is very great now! We wil choose this way in this tutorial
Let's first install some packages we need
$ cd frontend
$ npm install -D [email protected] [email protected] [email protected]
Notes:
- The
html-webpack-plugin
simplifies creation of HTML files to serve your webpack bundles. favicons-webpack-plugin
will leverage onfavicons
to automatically generate your favicons and Web app manifest.
Create frontend/src/index.html, leave it as empty file.
Put the original logo of your project to frontend/vendors/images/favicon.svg, PNG
and JPG
also supported.
As you can see, we will use this orange gear as our app icon.
Edit frontend/webpack/webpack.common.js
const HtmlWebpackPlugin = require('html-webpack-plugin');
const FaviconsWebpackPlugin = require('favicons-webpack-plugin');
plugins: [
...
new FaviconsWebpackPlugin({
// Your source logo (required)
logo: './vendors/images/favicon.svg',
// Prefix path for generated assets
prefix: 'assets/',
devMode: 'webapp', // optional can be 'webapp' or 'light' - 'light' by default
// Favicons configuration options. Read more on: https://github.com/evilebottnawi/favicons#usage
favicons: {
appName: 'AccordBox', // Your application's name. `string`
icons: {
favicons: true, // Create regular favicons. `boolean`
android: true, // Create Android homescreen icon. `boolean` or `{ offset, background }`
appleIcon: true, // Create Apple touch icons. `boolean` or `{ offset, background }`
appleStartup: false, // Create Apple startup images. `boolean` or `{ offset, background }`
coast: false, // Create Opera Coast icon. `boolean` or `{ offset, background }`
firefox: false, // Create Firefox OS icons. `boolean` or `{ offset, background }`
windows: false, // Create Windows 8 tile icons. `boolean` or `{ background }`
yandex: false // Create Yandex browser icon. `boolean` or `{ background }`
}
},
}),
new HtmlWebpackPlugin()
]
Notes:
- We import
HtmlWebpackPlugin
andFaviconsWebpackPlugin
at the top - And then we add two plugins, one is
FaviconsWebpackPlugin
and the other isHtmlWebpackPlugin
- As you can see, we add some config to the
FaviconsWebpackPlugin
to define how favicon is generated.
Let's rerun frontend command:
$ cd frontend
$ npm run watch
And we can see files below in the frontend/build
build
├── assets
│ ├── android-chrome-144x144.png
│ ├── android-chrome-192x192.png
│ ├── android-chrome-256x256.png
│ ├── android-chrome-36x36.png
│ ├── android-chrome-384x384.png
│ ├── android-chrome-48x48.png
│ ├── android-chrome-512x512.png
│ ├── android-chrome-72x72.png
│ ├── android-chrome-96x96.png
│ ├── apple-touch-icon-1024x1024.png
│ ├── apple-touch-icon-114x114.png
│ ├── apple-touch-icon-120x120.png
│ ├── apple-touch-icon-144x144.png
│ ├── apple-touch-icon-152x152.png
│ ├── apple-touch-icon-167x167.png
│ ├── apple-touch-icon-180x180.png
│ ├── apple-touch-icon-57x57.png
│ ├── apple-touch-icon-60x60.png
│ ├── apple-touch-icon-72x72.png
│ ├── apple-touch-icon-76x76.png
│ ├── apple-touch-icon-precomposed.png
│ ├── apple-touch-icon.png
│ ├── favicon-16x16.png
│ ├── favicon-32x32.png
│ ├── favicon-48x48.png
│ ├── favicon.ico
│ └── manifest.json
Notes:
- As you can see, the favicons are generated to the
build/assets
directory. android-chrome-X.png
are the PWA icons.apple-touch-icon
will be used to represent the app on the home scree of iOS devices.favicon-X.png
are the desktop favicons.
Let's check frontend/build/assets/manifest.json
, which is the web app manifest
.
{
"name": "AccordBox",
"short_name": "AccordBox",
"description": "Webpack boilerplate for Django & Flask",
"dir": "auto",
"lang": "en-US",
"display": "standalone",
"orientation": "any",
"start_url": "/?homescreen=1",
"background_color": "#fff",
"theme_color": "#fff",
"icons": [
{
"src": "android-chrome-36x36.png",
"sizes": "36x36",
"type": "image/png"
},
{
"src": "android-chrome-48x48.png",
"sizes": "48x48",
"type": "image/png"
},
{
"src": "android-chrome-72x72.png",
"sizes": "72x72",
"type": "image/png"
},
{
"src": "android-chrome-96x96.png",
"sizes": "96x96",
"type": "image/png"
},
{
"src": "android-chrome-144x144.png",
"sizes": "144x144",
"type": "image/png"
},
{
"src": "android-chrome-192x192.png",
"sizes": "192x192",
"type": "image/png"
},
{
"src": "android-chrome-256x256.png",
"sizes": "256x256",
"type": "image/png"
},
{
"src": "android-chrome-384x384.png",
"sizes": "384x384",
"type": "image/png"
},
{
"src": "android-chrome-512x512.png",
"sizes": "512x512",
"type": "image/png"
}
]
}
Then we check frontend/build/index.html
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>Webpack App</title>
<meta name="viewport" content="width=device-width, initial-scale=1">
<link rel="shortcut icon" href="/static/assets/favicon.ico">
<link rel="icon" type="image/png" sizes="16x16" href="/static/assets/favicon-16x16.png">
<link rel="icon" type="image/png" sizes="32x32" href="/static/assets/favicon-32x32.png">
<link rel="icon" type="image/png" sizes="48x48" href="/static/assets/favicon-48x48.png">
<link rel="manifest" href="/static/assets/manifest.json">
<meta name="mobile-web-app-capable" content="yes">
<meta name="theme-color" content="#fff">
<meta name="application-name" content="AccordBox">
<link rel="apple-touch-icon" sizes="57x57" href="/static/assets/apple-touch-icon-57x57.png">
<link rel="apple-touch-icon" sizes="60x60" href="/static/assets/apple-touch-icon-60x60.png">
<link rel="apple-touch-icon" sizes="72x72" href="/static/assets/apple-touch-icon-72x72.png">
<link rel="apple-touch-icon" sizes="76x76" href="/static/assets/apple-touch-icon-76x76.png">
<link rel="apple-touch-icon" sizes="114x114" href="/static/assets/apple-touch-icon-114x114.png">
<link rel="apple-touch-icon" sizes="120x120" href="/static/assets/apple-touch-icon-120x120.png">
<link rel="apple-touch-icon" sizes="144x144" href="/static/assets/apple-touch-icon-144x144.png">
<link rel="apple-touch-icon" sizes="152x152" href="/static/assets/apple-touch-icon-152x152.png">
<link rel="apple-touch-icon" sizes="167x167" href="/static/assets/apple-touch-icon-167x167.png">
<link rel="apple-touch-icon" sizes="180x180" href="/static/assets/apple-touch-icon-180x180.png">
<link rel="apple-touch-icon" sizes="1024x1024" href="/static/assets/apple-touch-icon-1024x1024.png">
<meta name="apple-mobile-web-app-capable" content="yes">
<meta name="apple-mobile-web-app-status-bar-style" content="black-translucent">
<meta name="apple-mobile-web-app-title" content="AccordBox">
<script defer src="/static/js/runtime.js"></script>
<script defer src="/static/js/vendors-node_modules_bootstrap_dist_js_bootstrap_bundle_js.js"></script>
<script defer src="/static/js/app.js"></script>
<script defer src="/static/js/app2.js"></script>
<link href="/static/css/app.css" rel="stylesheet">
</head>
<body>
</body>
</html>
Notes:
- The Webpack plugins are very powerful, they generated the app icons,
web app manifest
and the relevant HTML code. - We can copy the HTML to our Django template to make them work.
Let's update our django_pwa_app/templates/index.html
{% load webpack_loader %}
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8" />
<title>Index</title>
<link rel="shortcut icon" href="/static/assets/favicon.ico">
<link rel="icon" type="image/png" sizes="16x16" href="/static/assets/favicon-16x16.png">
<link rel="icon" type="image/png" sizes="32x32" href="/static/assets/favicon-32x32.png">
<link rel="icon" type="image/png" sizes="48x48" href="/static/assets/favicon-48x48.png">
<link rel="manifest" href="/static/assets/manifest.json">
<meta name="mobile-web-app-capable" content="yes">
<meta name="theme-color" content="#fff">
<meta name="application-name" content="AccordBox">
<link rel="apple-touch-icon" sizes="57x57" href="/static/assets/apple-touch-icon-57x57.png">
<link rel="apple-touch-icon" sizes="60x60" href="/static/assets/apple-touch-icon-60x60.png">
<link rel="apple-touch-icon" sizes="72x72" href="/static/assets/apple-touch-icon-72x72.png">
<link rel="apple-touch-icon" sizes="76x76" href="/static/assets/apple-touch-icon-76x76.png">
<link rel="apple-touch-icon" sizes="114x114" href="/static/assets/apple-touch-icon-114x114.png">
<link rel="apple-touch-icon" sizes="120x120" href="/static/assets/apple-touch-icon-120x120.png">
<link rel="apple-touch-icon" sizes="144x144" href="/static/assets/apple-touch-icon-144x144.png">
<link rel="apple-touch-icon" sizes="152x152" href="/static/assets/apple-touch-icon-152x152.png">
<link rel="apple-touch-icon" sizes="167x167" href="/static/assets/apple-touch-icon-167x167.png">
<link rel="apple-touch-icon" sizes="180x180" href="/static/assets/apple-touch-icon-180x180.png">
<link rel="apple-touch-icon" sizes="1024x1024" href="/static/assets/apple-touch-icon-1024x1024.png">
<meta name="apple-mobile-web-app-capable" content="yes">
<meta name="apple-mobile-web-app-status-bar-style" content="black-translucent">
<meta name="apple-mobile-web-app-title" content="AccordBox">
{% stylesheet_pack 'app' %}
</head>
<body>
<div class="bg-light py-5">
<div class="container">
<h1 class="display-3">Hello, world!</h1>
<p>This tutorial tutorial series will teach you how to bring PWA to Django</p>
<p><a class="btn btn-primary btn-lg d-none" href="#" role="button" id="installButton">Install PWA</a></p>
</div>
</div>
{% javascript_pack 'app' %}
</body>
</html>
(env)$ python manage.py runserver
We can open dev tools
in Chrome, and check Manifest
section in the Application
tab.
The above solution has below benefits
- It can help you generate PWA manifest and icons in easy way.
- It can also help generate icons for other devices (iOS), and you can also define some
theme
variable.
Resources
Conclusion
Django PWA Tutorial Series:
- Introduction
- Add Web app manifest to Django
- Add Service Worker to Django
- Fallback Offline Page in Django
- Caching and Routing (coming soon)
- How to add install button to PWA (coming soon)
- Send Web Push Notification from Django (part 1) (coming soon)
- Send Web Push Notification from Django (part 2) (coming soon)
The source code is available on https://github.com/AccordBox/django-pwa-demo