YouTip LogoYouTip

Django Blog Filter Search

Category Filtering and Keyword Search

\n\n

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

\n\n
\n\n

URL Query Parameters

\n\n

Query parameters are the part of the URL that comes after the question mark, such as /?category=django&q=Getting Started.

\n\n

In Django view functions, these parameters are accessed via request.GET.

\n\n

Example

\n\n
def index(request):\n    # request.GET is a dict-like object\n    # .get('key', 'default') safely retrieves the parameter, returning the default if it doesn't exist\n    category_slug = request.GET.get('category', '')  # category filter\n    keyword = request.GET.get('q', '')               # search keyword\n    print(f'Category: {category_slug}, Search: {keyword}')\n
\n\n
\n\n

ORM Chained Filtering and Q Objects

\n\n

Filtering and searching require dynamically concatenating query conditions.

\n\n

Example

\n\n
# File path: blog/views.py - update the index function\n\nfrom django.shortcuts import render\nfrom django.db.models import Q  # Q object for combining complex query conditions\nfrom .models import Post, Category\n\ndef index(request):\n    """Blog homepage: display article list, supports category filtering + keyword search"""\n    posts = Post.objects.all().order_by('-created_at')\n\n    # 1. Get query parameters\n    category_slug = request.GET.get('category', '')\n    keyword = request.GET.get('q', '')\n\n    # 2. Filter by category\n    if category_slug:\n        # category__slug: access the slug field of the category table via foreign key (double underscore)\n        posts = posts.filter(category__slug=category_slug)\n\n    # 3. Search by keyword (title or summary)\n    if keyword:\n        # Q(title__icontains=...) | Q(summary__icontains=...): OR combination\n        # icontains: case-insensitive containment query\n        posts = posts.filter(\n            Q(title__icontains=keyword) | Q(summary__icontains=keyword)\n        )\n\n    # 4. Get all categories (for rendering filter navigation)\n    categories = Category.objects.all()\n\n    context = {\n        'posts': posts,\n        'categories': categories,\n        'category_slug': category_slug,  # pass to template for highlighting current selection\n        'keyword': keyword,              # echo back search keyword\n        'title': 'TUTORIAL Blog - Home',\n    }\n    return render(request, 'blog/index.html', context)\n
\n\n

ORM Query Method Reference

\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n
MethodMeaningExample
field__exactExact matchtitle__exact='Django'
field__iexactCase-insensitive exact matchtitle__iexact='django'
field__containsContains (case-sensitive)title__contains='Getting Started'
field__icontainsContains (case-insensitive)title__icontains='django'
field__gte / lteGreater than or equal / Less than or equalcreated_at__gte='2024-01-01'
field__in=[...]In listcategory__in=[1, 2, 3]
\n\n
\n

The double underscore __ is a core convention in Django ORM: used to access fields of related models, or to specify the query type for a field. For example, category__slug means traversing the foreign key to the slug field in the Category table.

\n
\n\n
\n\n

Update the Homepage Template

\n\n

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

\n\n

Example

\n\n
<!-- File path: blog/templates/blog/index.html -->\n\n{% extends 'blog/base.html' %}\n\n{% block title %}{{ title }}{% endblock %}\n\n{% block content %}\n\n<h2 class="section-title">Latest Articles</h2>\n\n<!-- Search Box -->\n<div class="search-bar">\n    <!-- Form method="get": parameters appear in URL after submission (/search/?q=xxx) -->\n    <form method="get" action="{% url 'index' %}">\n        <input\n            type="text"\n            name="q"\n            value="{{ keyword }}"\n            placeholder="Search article title or summary..."\n            class="search-input"\n        />\n        {% if keyword %}\n        <a href="{% url 'index' %}" class="clear-btn">βœ•</a>\n        {% endif %}\n    </form>\n</div>\n\n<!-- Category Filter Navigation -->\n<div class="category-bar">\n    <!-- "All" button: without category parameter -->\n    <a href="{% url 'index' %}"\n       class="{% if not category_slug %}active{% endif %}">All</a>\n    {% for cat in categories %}\n    <a href="{% url 'index' %}?category={{ cat.slug }}{% if keyword %}&q={{ keyword }}{% endif %}"\n       class="{% if category_slug == cat.slug %}active{% endif %}">\n        {{ cat.name }}\n    </a>\n    {% endfor %}\n</div>\n\n<!-- Filter Result Count -->\n<p class="result-info">\n    {{ posts.count }} articles total\n    {% if keyword %}, searching for "{{ keyword }}"{% endif %}\n</p>\n\n{% if posts %}\n<div class="article-grid">\n    {% for post in posts %}\n    <a href="{% url 'post_detail' post.pk %}" class="card-link">\n        <div class="article-card">\n            <div class="card-content">\n                <span class="card-category">{{ post.category.name }}</span>\n                <h3>{{ post.title }}</h3>\n                <p>{{ post.summary|truncatechars:80 }}</p>\n                <span class="card-date">{{ post.created_at|date:"Y-m-d" }}</span>\n            </div>\n        </div>\n    </a>\n    {% endfor %}\n</div>\n{% else %}\n<p class="empty-tip">No matching articles found</p>\n{% endif %}\n\n{% endblock %}\n
\n\n

Filter Navigation and Search Box Styles

\n\n

Example

\n\n
/* Append to base.html style */\n\n.search-bar {\n    margin-bottom: 20px;\n}\n\n.search-input {\n    width: 100%;\n    padding: 12px 16px;\n    border: 2px solid #eee;\n    border-radius: 8px;\n    font-size: 15px;\n    outline: none;\n    transition: border-color 0.2s;\n}\n\n.search-input:focus {\n    border-color: #2c3e50;\n}\n\n.clear-btn {\n    position: absolute;\n    right: 12px;\n    top: 50%;\n    transform: translateY(-50%);\n    color: #999;\n    text-decoration: none;\n    font-size: 18px;\n}\n\n.category-bar {\n    display: flex;\n    gap: 10px;\n    margin-bottom: 16px;\n    flex-wrap: wrap;\n}\n\n.category-bar a {\n    padding: 6px 16px;\n    border: 1px solid #ddd;\n    border-radius: 20px;\n    text-decoration: none;\n    color: #333;\n    font-size: 14px;\n    transition: all 0.2s;\n}\n\n.category-bar a.active {\n    background: #2c3e50;\n    color: #fff;\n    border-color: #2c3e50;\n}\n\n.category-bar a:hover {\n    border-color: #2c3e50;\n}\n\n.result-info {\n    color: #999;\n    font-size: 14px;\n    margin-bottom: 16px;\n}\n\n.card-link {\n    text-decoration: none;\n    color: inherit;\n}\n
\n\n
\n

The category button links preserve both category and keyword parameters: ?category={{ cat.slug }}&q={{ keyword }}. This way, when a user searches first and then clicks a category, the search condition is not lost β€” both conditions apply simultaneously.

\n
\n\n
\n\n

Hands-on: Test Filtering and Search

\n\n

Start the server and verify the following:

\n\n
    \n
  • Clicking a category button only shows articles from that category
  • \n
  • Entering a keyword filters titles and summaries in real-time
  • \n
  • Category + SearchCombine: filtered results satisfy both conditions simultaneously
  • \n
  • The "All" button clears the category filter and restores display of all articles
  • \n
\n\n
\n\n

Chapter Summary

\n\n

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

\n\n

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

← Vue3 Blog Watch SearchDjango Blog Views Templates β†’