Django Favicon Guide

Table of Contents

Objectives

In this tutorial, I will talk about the two solutions to add Favicon to your Django project.

Favicon

A favicon, also known as a shortcut icon, website icon, tab icon, URL icon, or bookmark icon, is a file containing one or more small icons,[1] associated with a particular website or web page

Many people know favicon because they can see them on the tab of the browsers, or the mobile screen.

However, Favicon is not good enough, and some company also created some private standards (as many developers do not even know).

Apple touch icon

Apple touch icon: Similar to the Favicon, the Apple touch icon or apple-touch-icon.png is a file used for a web page icon on the Apple iPhone, iPod Touch, and iPad. When someone bookmarks your web page or adds your web page to their home screen, this icon is used.

Web app manifest

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.

This is created by Google, and you can check more details on https://web.dev/add-manifest/

Solution 1:

There are some great favicon generators available online. We can use them to help us generate icon files and HTML. And then import them to our Django project.

Create Django Project

$ mkdir favicon_project && cd favicon_project

$ python3.10 -m venv venv
$ source venv/bin/activate
(venv)$
(venv)$ pip install django==4.1
(venv)$ django-admin startproject favicon_project .
(venv)$ python manage.py migrate
(venv)$ 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.

Templates

Updater favicon_project/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 favicon_project/templates

Update TEMPLATES in favicon_project/settings.py, so Django can know where to find the templates

TEMPLATES = [
    {
        'BACKEND': 'django.template.backends.django.DjangoTemplates',
        'DIRS': ['favicon_project/templates'],
        '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 favicon_project/templates/index.html

<h1>Home</h1>
(venv)$ python manage.py runserver

Now you should be able to see Home on the http://127.0.0.1:8000/

Static

$ mkdir favicon_project/static

.
├── db.sqlite3
├── favicon_project
│   ├── __init__.py
│   ├── asgi.py
│   ├── settings.py
│   ├── static                     # new
│   ├── templates
│   │   └── index.html
│   ├── urls.py
│   └── wsgi.py
└── manage.py

Update favicon_project/settings.py

STATIC_URL = '/static/'
STATICFILES_DIRS = [
    str(BASE_DIR / 'favicon_project' / 'static'),           # new
]
  1. Here we create favicon_project/static directory.
  2. We will later put favicons in the above directory so Django can server the files.

Generate Favicon

Here, we use https://realfavicongenerator.net/ to help us generate Favicon. You can also use other 3-party services as you like.

First, click link above and upload our original favicon image.

And then, check the image on different devices, and update settings if we need.

In this case, I set #ffffff as background color for Favicon for iOS because the black background does not look good with the orange gear icon.

After we confirm, we wil wait some seconds, the favicon package and the HTML code will be available.

Import Favicons to Django

In the downloaded package, we can see

.
├── android-chrome-192x192.png
├── android-chrome-512x512.png
├── apple-touch-icon.png
├── browserconfig.xml
├── favicon-16x16.png
├── favicon-32x32.png
├── favicon.ico
├── mstile-144x144.png
├── mstile-150x150.png
├── mstile-310x150.png
├── mstile-310x310.png
├── mstile-70x70.png
├── safari-pinned-tab.svg
└── site.webmanifest

Copy all the files to favicon_project/static directory.

.
├── Pipfile
├── Pipfile.lock
├── db.sqlite3
├── favicon_project
│   ├── __init__.py
│   ├── asgi.py
│   ├── settings.py
│   ├── static
│   │   ├── android-chrome-192x192.png
│   │   ├── android-chrome-512x512.png
│   │   ├── apple-touch-icon.png
│   │   ├── browserconfig.xml
│   │   ├── favicon-16x16.png
│   │   ├── favicon-32x32.png
│   │   ├── favicon.ico
│   │   ├── mstile-144x144.png
│   │   ├── mstile-150x150.png
│   │   ├── mstile-310x150.png
│   │   ├── mstile-310x310.png
│   │   ├── mstile-70x70.png
│   │   ├── safari-pinned-tab.svg
│   │   └── site.webmanifest
│   ├── templates
│   │   └── index.html
│   ├── urls.py
│   └── wsgi.py
└── manage.py

And then copy the HTML from realfavicongenerator.net to Django template.

{% load static %}
<!doctype html>
<html lang="en">
  <head>
    <link rel="apple-touch-icon" sizes="180x180" href="{% static 'apple-touch-icon.png' %}">
    <link rel="icon" type="image/png" sizes="32x32" href="{% static 'favicon-32x32.png' %}">
    <link rel="icon" type="image/png" sizes="16x16" href="{% static 'favicon-16x16.png' %}">
    <link rel="manifest" href="{% static 'site.webmanifest' %}">
    <link rel="mask-icon" href="{% static 'safari-pinned-tab.svg' %}" color="#5bbad5">
    <meta name="msapplication-TileColor" content="#da532c">
    <meta name="theme-color" content="#ffffff">
  </head>
  <body>
    <h1>Home</h1>
  </body>
</html>

Notes:

  1. Here we update the href from /apple-touch-icon.png to {% static 'apple-touch-icon.png' %}
  2. href="/static/apple-touch-icon.png" can also work.

Update site.webmanifest

If we check the site.webmanifest, we can see:

{
    "name": "",
    "short_name": "",
    "icons": [
        {
            "src": "/android-chrome-192x192.png",
            "sizes": "192x192",
            "type": "image/png"
        },
        {
            "src": "/android-chrome-512x512.png",
            "sizes": "512x512",
            "type": "image/png"
        }
    ],
    "theme_color": "#ffffff",
    "background_color": "#ffffff",
    "display": "standalone"
}

Notes:

  1. To know more about web app manifest, please check Add a web app manifest
  2. We can delete the leading slash of the src value to make it work.

Solution 2:

The above solution contains some manual operations.

Now the frontend community has some good packages, and we will use them to help us get this job done.

Create Django Project

$ mkdir favicon_project && cd favicon_project

$ python3.10 -m venv venv
$ source venv/bin/activate
(venv)$
(venv)$ pip install django==4.1
(venv)$ django-admin startproject favicon_project .
(venv)$ python manage.py migrate
(venv)$ 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 can let you play with modern frontend tech in Django, within minutes.

(venv)$ pip install python-webpack-boilerplate==1.0.0

Add webpack_boilerplate to the INSTALLED_APPS in favicon_project/settings.py

Let's run Django command to create frontend project from the templates

$ python manage.py webpack_init
# here we use the default values
project_slug [frontend]:
run_npm_command_at_root [n]:

Now you should see frontend directory is created.

├── db.sqlite3
├── favicon_project
│   ├── __init__.py
│   ├── 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
│   │       └── 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
v16.13.1
$ npm -v
8.1.2
$ cd frontend
# install dependency packages
$ npm install
# run webpack in watch mode
$ npm run watch

Add code below to Django settings favicon_project/settings.py

STATICFILES_DIRS = [
    BASE_DIR / "frontend/build",
]

WEBPACK_LOADER = {
    'MANIFEST_FILE': 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.

Templates

Updater favicon_project/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 favicon_project/templates

Update TEMPLATES in favicon_project/settings.py, so Django can know where to find the templates

TEMPLATES = [
    {
        'BACKEND': 'django.template.backends.django.DjangoTemplates',
        'DIRS': ['favicon_project/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',
            ],
        },
    },
]

Load webpack bundle file

Create favicon_project/templates/index.html

{% load webpack_loader static %}

<!DOCTYPE html>
<html>
<head>
  <meta charset="utf-8" />
  <title>Index</title>
  {% stylesheet_pack 'app' %}
</head>
<body>

<h1>Home</h1>

{% 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
# please make sure `npm run watch` is still running
(venv)$ python manage.py runserver

Now you should be able to see Home on the http://127.0.0.1:8000/

favicons package

Next, we will use favicons to help us generate different sizes of favicons and their associated files.

$ cd frontend
$ npm install -D [email protected] [email protected] [email protected]

Create frontend/src/index.html, leave it as empty file.

Put the original svg file to frontend/vendors/images/favicon.svg, PNG and JPG also ok.

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',
    // 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.
$ cd frontend
$ npm run build

# now we can see the generated images under build/assets
├── 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. Since we already add BASE_DIR / "frontend/build", to the STATICFILES_DIRS, so the files in assets can be served as normal static files by Django (or whitenoise in the production)

If we check frontend/build/index.html and format the 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="defer" src="/static/js/runtime.589add24.js"></script>
  <script defer="defer" src="/static/js/577.87ba1327.js"></script>
  <script defer="defer" src="/static/js/app.992787b3.js"></script>
  <script defer="defer" src="/static/js/app2.6d31e388.js"></script>
  <link href="/static/css/app.56d0d2493713d97f0c40.css" rel="stylesheet">
</head>
<body></body>
</html>

As you can see, this is very similar with the HTML we download from the above realfavicongenerator.net

We can copy the HTML of icons to the Django template, and it should work well.

Notes:

  1. By default, only npm run build (production build) will generate the favicon.
  2. We can generate favicons when we deploy our project.

Which One is Better

  1. If you need a simple solution and do not want to touch the NPM stuff, then solution 1 is good option.
  2. If you need a solution which can give you more control, then the NPM package can help you better solve the problem here.

How to Update Favicon

Some devices might cache the favicons, in some cases, it makes it a little hard.

Some people recommend adding querystring to the URL, however, based on my experience, this method might fail in some cases, and it is not recommended.

Here I can tell you another way to solve this problem.

You can put favicons and manifest.json in a specific directory such as assets-VERSION directory.

Then if you rename the directory, the cache image will not be used because the whole URL has changed.

Make Favicon Work with Both Light/Dark Theme

I recommend you to take a look at Building an adaptive favicon

Want Fewer Icons?

Please read How to Favicon: Six files that fit most needs

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