Skip to content

Chapter 7: Template System ​

7.3 Custom Templates ​

Custom Filters ​

Django allows you to create custom filters to extend template functionality. A custom filter is a Python function that takes at most two parameters:

  1. The value of the variable (input)
  2. The value of the parameter (optional)

First, create a templatetags directory in your app directory and add an __init__.py file:

myapp/
    templatetags/
        __init__.py
        custom_filters.py

Then define filters in custom_filters.py:

python
from django import template

register = template.Library()

# Simple filter example
@register.filter
def lower(value):
    """Convert string to lowercase"""
    return value.lower()

# Filter with parameters example
@register.filter
def truncate_chars(value, max_length):
    """Truncate string to specified character count"""
    if len(value) > max_length:
        return value[:max_length] + '...'
    return value

# More complex filter example
@register.filter
def word_count(value):
    """Count words in text"""
    return len(value.split())

@register.filter
def multiply(value, arg):
    """Multiply value by argument"""
    try:
        return float(value) * float(arg)
    except (ValueError, TypeError):
        return 0

@register.filter
def highlight_search(text, search_term):
    """Highlight search keywords"""
    if search_term:
        return text.replace(
            search_term, 
            f'<mark>{search_term}</mark>'
        )
    return text

Using custom filters in templates:

html
{% load custom_filters %}

<p>Title: {{ title|lower }}</p>
<p>Summary: {{ content|truncate_chars:100 }}</p>
<p>Word count: {{ content|word_count }}</p>
<p>Price: {{ price|multiply:1.2|floatformat:2 }}</p>
<p>Search results: {{ content|highlight_search:query }}</p>

Custom Tags ​

Custom tags are more complex than filters and can execute more complex logic. They come in several types:

  1. Simple tags
  2. Inclusion tags
  3. Assignment tags

Define tags in templatetags/custom_tags.py:

python
from django import template
from django.utils.safestring import mark_safe
from ..models import Article, Category

register = template.Library()

# Simple tag example
@register.simple_tag
def total_articles():
    """Return total number of articles"""
    return Article.objects.count()

@register.simple_tag
def article_count_by_category(category_slug):
    """Return number of articles in specified category"""
    try:
        category = Category.objects.get(slug=category_slug)
        return category.articles.count()
    except Category.DoesNotExist:
        return 0

# Simple tag with parameters
@register.simple_tag(takes_context=True)
def user_article_count(context):
    """Return number of articles by current user"""
    user = context['user']
    if user.is_authenticated:
        return Article.objects.filter(author=user).count()
    return 0

# Inclusion tag example
@register.inclusion_tag('tags/recent_articles.html')
def recent_articles(count=5):
    """Display recent articles"""
    articles = Article.objects.filter(
        status='published'
    ).order_by('-publish_date')[:count]
    return {'articles': articles}

# Assignment tag example
@register.simple_tag
def get_categories():
    """Get all categories"""
    return Category.objects.all()

# Complex tag example
@register.tag
def highlight_text(parser, token):
    """
    Highlight text tag
    Usage: {% highlight_text "search term" %}text to search{% endhighlight_text %}
    """
    try:
        # Parse tag parameters
        tag_name, search_term = token.split_contents()
    except ValueError:
        raise template.TemplateSyntaxError(
            "%r tag requires a single argument" % token.contents.split()[0]
        )
    
    # Parse tag content until endhighlight_text
    nodelist = parser.parse(('endhighlight_text',))
    parser.delete_first_token()
    
    return HighlightTextNode(nodelist, search_term)

class HighlightTextNode(template.Node):
    def __init__(self, nodelist, search_term):
        self.nodelist = nodelist
        self.search_term = template.Variable(search_term)
    
    def render(self, context):
        # Render tag content
        text = self.nodelist.render(context)
        try:
            search_term = self.search_term.resolve(context)
            # Highlight search term
            highlighted = text.replace(
                search_term,
                f'<mark>{search_term}</mark>'
            )
            return mark_safe(highlighted)
        except template.VariableDoesNotExist:
            return text

Using custom tags in templates:

html
{% load custom_tags %}

<!-- Simple tags -->
<p>Total articles: {% total_articles %}</p>
<p>Python category articles: {% article_count_by_category "python" %}</p>
<p>Your articles: {% user_article_count %}</p>

<!-- Inclusion tags -->
{% recent_articles 10 %}

<!-- Assignment tags -->
{% get_categories as categories %}
<ul>
{% for category in categories %}
    <li>{{ category.name }}</li>
{% endfor %}
</ul>

<!-- Complex tags -->
{% highlight_text "Django" %}
Django is a high-level Python Web framework that encourages rapid development and clean, pragmatic design.
{% endhighlight_text %}

Template file for inclusion tag templates/tags/recent_articles.html:

html
<div class="recent-articles">
    <h3>Recent Articles</h3>
    <ul>
    {% for article in articles %}
        <li>
            <a href="{{ article.get_absolute_url }}">{{ article.title }}</a>
            <span class="date">{{ article.publish_date|date:"m-d" }}</span>
        </li>
    {% empty %}
        <li>No articles available</li>
    {% endfor %}
    </ul>
</div>

Template Context Processors ​

Template context processors are functions that automatically add variables to all template contexts. They are defined in the TEMPLATES configuration in settings.py.

Create context_processors.py:

python
from django.conf import settings

def site_info(request):
    """Site information context processor"""
    return {
        'site_name': getattr(settings, 'SITE_NAME', 'My Website'),
        'site_description': getattr(settings, 'SITE_DESCRIPTION', 'A Django website'),
    }

def user_permissions(request):
    """User permissions context processor"""
    if request.user.is_authenticated:
        return {
            'user_permissions': request.user.get_all_permissions(),
            'is_admin': request.user.is_staff,
        }
    return {}

def common_data(request):
    """Common data context processor"""
    from ..models import Category
    
    try:
        categories = Category.objects.all()[:10]
    except:
        categories = []
    
    return {
        'common_categories': categories,
        'current_year': datetime.now().year,
    }

Register context processors in settings.py:

python
TEMPLATES = [
    {
        'BACKEND': 'django.template.backends.django.DjangoTemplates',
        'DIRS': [BASE_DIR / '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',
                # Custom context processors
                'myapp.context_processors.site_info',
                'myapp.context_processors.user_permissions',
                'myapp.context_processors.common_data',
            ],
        },
    },
]

Using variables provided by context processors in any template:

html
<!-- All templates can access these variables -->
<header>
    <h1>{{ site_name }}</h1>
    <p>{{ site_description }}</p>
</header>

<nav>
    <ul>
    {% for category in common_categories %}
        <li><a href="{{ category.get_absolute_url }}">{{ category.name }}</a></li>
    {% endfor %}
    </ul>
</nav>

<footer>
    <p>&copy; {{ current_year }} {{ site_name }}</p>
    {% if is_admin %}
    <p><a href="/admin/">Admin</a></p>
    {% endif %}
</footer>

Static File Handling ​

Correctly referencing static files (CSS, JavaScript, images, etc.) in templates:

html
{% load static %}

<!DOCTYPE html>
<html>
<head>
    <title>My Website</title>
    <!-- CSS files -->
    <link rel="stylesheet" type="text/css" href="{% static 'css/style.css' %}">
    <link rel="stylesheet" type="text/css" href="{% static 'css/responsive.css' %}">
</head>
<body>
    <!-- Images -->
    <img src="{% static 'images/logo.png' %}" alt="Logo">
    
    <div class="content">
        <!-- Page content -->
    </div>
    
    <!-- JavaScript files -->
    <script src="{% static 'js/jquery.min.js' %}"></script>
    <script src="{% static 'js/main.js' %}"></script>
    
    <!-- Static files with version control -->
    <script src="{% static 'js/app.js' %}?v=1.0"></script>
</body>
</html>

In development environment, configure static files in settings.py:

python
# settings.py
STATIC_URL = '/static/'
STATICFILES_DIRS = [
    BASE_DIR / "static",
]
STATIC_ROOT = BASE_DIR / "staticfiles"

In production environments, it's usually necessary to configure a web server (such as Nginx) to serve static files directly to improve performance.

Summary ​

Custom template functionality extends the capabilities of the Django template system:

  1. ✅ Custom filters handle variable formatting
  2. ✅ Custom tags implement complex logic
  3. ✅ Template context processors globally inject variables
  4. ✅ Proper referencing and management of static files
  5. ✅ Flexible use of inclusion tags and assignment tags

Mastering custom template functionality can create more powerful and flexible frontend interfaces.

Next ​

We will learn about the Django form system.

8.1 Form Basics →

Contents ​

Back to Course Outline

Released under the [BY-NC-ND License](https://creativecommons.org/licenses/by-nc-nd/4.0/deed.en).