Add a favicon, company logo and footer with cookie statement and privacy policy

Some elements should be part of every site, such as favicon, company logo, cookie statement and privacy policy. This tutorial will cover them.

July 10, 2020, 8:53 a.m.
Themes: Navigation

This tutorial will be about hygiene. Adding a favicon is easy, and adding a company logo to an existing navigation menu is not difficult either. Adding a footer that neatly sticks to the bottom and is configurable in admin requires a bit more attention. Let's start with the favicon. You can use several filetypes for this, our icon file will be favicon-32x32.png. In your base.html template add:

<link rel="icon" type="image/png" href="{% static 'images/favicon-32x32.png' %}"/>

That means Django will look for the file favicon-32x32.png in a directory images in one of the paths in your STATICFILES_DIRS. The web offers a lot of help creating a favicon, so let's assume we have one. We put it in our images directory of our project directory and we're done.

It is perfectly possible to hardcode the logo in our navigation menu, but let's use the Wagtail editor to make things a bit more flexible. Create a snippet in models.py:

@register_snippet
class CompanyLogo(models.Model):
    name = models.CharField(max_length=250)
    logo = models.ForeignKey(
        'wagtailimages.Image', on_delete=models.CASCADE, related_name='+'
    )

    panels = [
        FieldPanel('name', classname='full'),
        ImageChooserPanel('logo'),
    ]

    def __str__(self):
        return self.name

This will give us a name and image that we can define / upload in our editor. The image is defined in the usual way, with related_name='+' to prevent a backwards relation. Now we create a tag in the file cms_tags.py (or define your own name) in the directory templatetags of your app:

from django import template
from cms.models import CompanyLogo

register = template.Library()

@register.simple_tag()
def company_logo():
    return CompanyLogo.objects.first()

We use Django's simple_tag and assume that we just have one logo, so return the first object. Of course we can extend this if necessary for more elaborate use, such as different types of logos. In our template, in our case main_menu.html, we load the tag module and name the tag:

{% load cms_tags %}
{% company_logo as logo %}

and then we can add the logo to the template, if desired with a link to the home page for example:

<a class="navbar-brand" href="/">{% image logo.logo fill-30x30 %}{{ logo.name }}</a>

Add some styling to your CSS file in a way you see fit, for example:

.navbar .navbar-brand {
    font-family: 'IM Fell English', serif;
    font-size: xx-large;
    color: navy;
}

Add the font to the head of base.html as well:

<link href="https://fonts.googleapis.com/css2?family=IM+Fell+English:ital@1&display=swap" rel="stylesheet">

Our footer is essentially a menu. We could hardcode it, but in a previous tutorial we have created a function for this, so let's use that. We want to have links to pages with our cookie statement and privacy policy, which contain in their simplest form just text, so let's create a page model for that, using wagtailtrans's TranslatablePage to make it available in multiple languages:

class TextPage(TranslatablePage):
    text = RichTextField(blank=True)

    content_panels = TranslatablePage.content_panels + [
        FieldPanel('text', classname='full'),
    ]

We need a template for this model, which (as always) is called text_page.html. The content is straightforward:

{% extends "base.html" %}

{% load wagtailcore_tags %}

{% block body_class %}template-textpage{% endblock %}

{% block content %}

    <div class="container-fluid mt-4">
        <div>
            <h3>{{ page.title|richtext }}</h3>
        </div>
        <div>
            {{ page.text|richtext }}
        </div>
    </div>

{% endblock %}

In our base.html we will include a template footer.html for the footer, which is also straightforward:

{% load cms_tags %}

{% get_menu "footer" None request.user.is_authenticated as footer %}

<footer>
    <ul>
        <li>
            {% for item in footer %}
                {%  if item.url %}
                    <a href="{{ item.url }}">{{ item.title }}</a>
                {% else %}
                    <span>{{ item.title }}</span>
                {%  endif %}
            {% endfor %}
        </li>
    </ul>
</footer>

The None argument corresponds to the page argument of the get_menu function, which we don't use here. We don't really need to know whether the user is logged in, but we do need the third argument for the function get_menu, that's why it's there. Add the following line to your base.html file at the end of the body:

{% include "footer.html" %}

There are several ways to position the footer. We would like it at the bottom of the page, not visible if the page is longer than the viewport, but at the bottom of the viewport if the page is shorter than the viewport (not halfway the viewport just below the text). A simple way to achieve this is to create some space at the end of the body (e.g. 50 px) and put the footer there:

html {
    height: 100%;
}
body {
    min-height: 100%;
    position: relative;
    padding-bottom: 50px;
}
footer {
    background-color: navy;
    height: 50px;
    padding-top: 10px;
    position: absolute;
    bottom: 0;
    width: 100%;
}
footer ul {
    list-style-type: none;
}
footer li span, footer li a, footer li a:hover {
    display: inline;
    color: white;
    font-size: 12px;
    margin-right: 40px;
}

Migrate your database to add the models and in admin create two pages from the TextPage model, one with a cookie statement and one with a privacy policy. Still in admin create a menu footer in the same way we have done in that earlier tutorial, and add a copyright menu item with no link, a cookie statement menu item and a privacy policy menu item with links to their respective pages. That should do it. Visit some pages and see if you like the result.

So far for now. If you would like to know how to translate the items in the footer menu, read on.

Comment on this article (sign in first or confirm by name and email below)