Category filtering and keyword search

In this chapter, you will learn to handle URL query parameters, implement category filtering navigation, and keyword search functionality.


URL query parameters

Query parameters are the part of the URL after the question mark, such as/?category=django&q=入门。

In Django view functions, userequest.GETGet these parameters.

Example

def index(request):
    # request.GET is a dictionary-like object
    # .get('key', 'Default value') safely get parameter, return default if it does not exist
    category_slug = request.GET.get('category', '')   # Category filter
    keyword = request.GET.get('q', '')                 # Search Keywords
    print(f'Category: {category_slug}, Search: {keyword}')

ORM chained filtering and Q objects

Filtering and searching require dynamically assembling query conditions.

Example

# File path: blog/views.py, update the index function
from django.shortcuts import render
from django.db.models import Q           # Q objects are used to combine complex query conditions.
from .models import Post, Category

def index(request):
    """Blog home page: display article list, support category filtering + keyword search"""
    posts = Post.objects.all().order_by('-created_at')

    # 1. Get query parameters
    category_slug = request.GET.get('category', '')
    keyword = request.GET.get('q', '')

    # 2. Filter by category
    if category_slug:
        # category__slug: access the slug field of the category table through the foreign key (double underscore)
        posts = posts.filter(category__slug=category_slug)

    # 3. Search by keyword (title or summary)
    if keyword:
        # Q(title__icontains=...) | Q(summary__icontains=...): OR combination
        # icontains: case-insensitive contains query
        posts = posts.filter(
            Q(title__icontains=keyword) | Q(summary__icontains=keyword)
        )

    # 4. Get all categories (for rendering filter navigation)
    categories = Category.objects.all()

    context = {
        'posts': posts,
        'categories': categories,
        'category_slug': category_slug,   # Passed to the template to highlight the current selection
        'keyword': keyword,               # Echo search keywords
        'title': 'EXAMPLE Blog - Home',
    }
    return render(request, 'blog/index.html', context)

Explanation of ORM query methods

MethodsMeaningExample
field__exactExact matchtitle__exact='Django'
field__iexactCase-insensitive exact matchtitle__iexact='django'
field__containsContains (case-sensitive)title__contains='入门'
field__icontainsContains (case-insensitive)title__icontains='django'
field__gte / lteGreater than or equal to / Less than or equal tocreated_at__gte='2024-01-01'
field__in=[...]In the listcategory__in=[1, 2, 3]

Double underscore__is a core convention of Django ORM: used to access fields of related models, or specify how to query fields. For examplecategory__slugrepresents crossing the foreign key to the slug field of the Category table.


Update the homepage template

Add category filtering navigation and a search box above the article list.

Example

<!-- File path: blog/templates/blog/index.html -->
{% extends 'blog/base.html' %}

{% block title %}{{ title }}{% endblock %}

{% block content %}
<h2 class="section-title">Latest Articles</h2>

<!-- Search box -->
<div class="search-bar">
    <!-- Table单 method="get":SubmitafterParameterappears in In the URL(/search/?q=xxx) -->
    <form method="get" action="{% url 'index' %}">
        <input
           type="text"
           name="q"
           value="{{ keyword }}"
           placeholder=Search article titles or summaries...
           class="search-input"
       />
        {% if keyword %}
        <a href="{% url 'index' %}" class="clear-btn">✕</a>
        {% endif %}
    </form>
</div>

<!-- Category filter navigation -->
<div class="category-bar">
    <!-- 'All' button: without category parameter -->
    <a href="{% url 'index' %}"
      class="{% if not category_slug %}active{% endif %}">All</a>

    {% for cat in categories %}
    <a href="{% url 'index' %}?category={{ cat.slug }}{% if keyword %}&q={{ keyword }}{% endif %}"
      class="{% if category_slug == cat.slug %}active{% endif %}">
        {{ cat.name }}
    </a>
    {% endfor %}
</div>

<!-- Filter result statistics -->
<p class="result-info">
Total {{ posts.count }} posts
{% if keyword %}, searching "{{ keyword }}"{% endif %}
</p>

{% if posts %}
    <div class="article-grid">
        {% for post in posts %}
        <a href="{% url 'post_detail' post.pk %}" class="card-link">
            <div class="article-card">
                <div class="card-content">
                    <span class="card-category">{{ post.category.name }}</span>
                    <h3>{{ post.title }}</h3>
                    <p>{{ post.summary|truncatechars:80 }}</p>
                    <span class="card-date">{{ post.created_at|date:"Y-m-d" }}</span>
                </div>
            </div>
        </a>
        {% endfor %}
    </div>
{% else %}
    <p class="empty-tip">No matching articles found</p>
{% endif %}
{% endblock %}

Filter navigation and search box styles

Example

/* Append to the style in base.html */
.search-bar {
    margin-bottom: 20px;
}

.search-input {
    width: 100%;
    padding: 12px 16px;
    border: 2px solid #eee;
    border-radius: 8px;
    font-size: 15px;
    outline: none;
    transition: border-color 0.2s;
}

.search-input:focus {
    border-color: #2c3e50;
}

.clear-btn {
    position: absolute;
    right: 12px;
    top: 50%;
    transform: translateY(-50%);
    color: #999;
    text-decoration: none;
    font-size: 18px;
}

.category-bar {
    display: flex;
    gap: 10px;
    margin-bottom: 16px;
    flex-wrap: wrap;
}

.category-bar a {
    padding: 6px 16px;
    border: 1px solid #ddd;
    border-radius: 20px;
    text-decoration: none;
    color: #333;
    font-size: 14px;
    transition: all 0.2s;
}

.category-bar a.active {
    background: #2c3e50;
    color: #fff;
    border-color: #2c3e50;
}

.category-bar a:hover {
    border-color: #2c3e50;
}

.result-info {
    color: #999;
    font-size: 14px;
    margin-bottom: 16px;
}

.card-link {
    text-decoration: none;
    color: inherit;
}

The category button links also retaincategoryandkeywordParameters:?category={{ cat.slug }}&q={{ keyword }}. This way, after users search first and then click a category, the search condition is not lost, and both conditions take effect simultaneously.


Hands-on: Test filtering and search

Start the server and verify the following points:

  • Click the category button to display only articles of the corresponding category.
  • Enter keywords to filter titles and summaries in real time.
  • Category + search stacking: filtered results satisfy both conditions simultaneously
  • The "All" button clears the category filter and restores display of all articles.

Chapter summary

In this chapter, you mastered the methods of handling URL query parameters in Django: using request.GET.get() to obtain parameters, ORM chained filtering + Q objects to implement complex queries, and dynamically concatenating URL parameters in templates to implement filter links.

Category filtering and search functionality allow users to quickly find articles of interest.

other extensions