Add Web app manifest to Django

Table of Contents

Django PWA Tutorial Series:

  1. Introduction
  2. Add Web app manifest to Django
  3. Add Service Worker to Django
  4. Fallback Offline Page in Django
  5. Caching and Routing (coming soon)
  6. How to add install button to PWA (coming soon)
  7. Send Web Push Notification from Django (part 1) (coming soon)
  8. 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:

  1. Understand what is Web app manifest and how to generate app icons using NPM package.
  2. 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

  1. On nodejs homepage
  2. Using nvm I recommend this way.
$ 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"),
}
  1. We add the above frontend/build to STATICFILES_DIRS so Django can find the static assets (img, font and others)
  2. We add MANIFEST_FILE location to the WEBPACK_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>
  1. We load webpack_loader at the top of the template
  2. We use stylesheet_pack and javascript_pack to load CSS and JS bundle files to Django, both of them come from python-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.

  1. One way is to use some online services, For example: https://realfavicongenerator.net/, and then download the files.
  2. 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:

  1. The html-webpack-plugin simplifies creation of HTML files to serve your webpack bundles.
  2. favicons-webpack-plugin will leverage on favicons 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:

  1. We import HtmlWebpackPlugin and FaviconsWebpackPlugin at the top
  2. And then we add two plugins, one is FaviconsWebpackPlugin and the other is HtmlWebpackPlugin
  3. 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:

  1. As you can see, the favicons are generated to the build/assets directory.
  2. android-chrome-X.png are the PWA icons.
  3. apple-touch-icon will be used to represent the app on the home scree of iOS devices.
  4. 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:

  1. The Webpack plugins are very powerful, they generated the app icons, web app manifest and the relevant HTML code.
  2. 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

  1. It can help you generate PWA manifest and icons in easy way.
  2. It can also help generate icons for other devices (iOS), and you can also define some theme variable.

Resources

  1. Example of InjectManifest in Workbox v5

Conclusion

Django PWA Tutorial Series:

  1. Introduction
  2. Add Web app manifest to Django
  3. Add Service Worker to Django
  4. Fallback Offline Page in Django
  5. Caching and Routing (coming soon)
  6. How to add install button to PWA (coming soon)
  7. Send Web Push Notification from Django (part 1) (coming soon)
  8. Send Web Push Notification from Django (part 2) (coming soon)

The source code is available on https://github.com/AccordBox/django-pwa-demo

Launch Products Faster with Django

Unlock the power of Django combined with Hotwire through SaaS Hammer. Supercharge productivity, tap into Python's rich ecosystem, and focus on perfecting your product!

Michael Yin

Michael Yin

Michael is a Full Stack Developer who loves writing code, tutorials about Django, and modern frontend techs.

He has published some tech course on testdriven.io and ebooks on leanpub.

© 2024 SaaS Hammer