Category Filtering and Keyword Search
\n\nIn 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\nQuery parameters are the part of the URL that comes after the question mark, such as /?category=django&q=Getting Started.
In Django view functions, these parameters are accessed via request.GET.
Example
\n\ndef 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\nFiltering and searching require dynamically concatenating query conditions.
\n\nExample
\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\nORM Query Method Reference
\n\n| Method | \nMeaning | \nExample | \n
|---|---|---|
field__exact | \nExact match | \ntitle__exact='Django' | \n
field__iexact | \nCase-insensitive exact match | \ntitle__iexact='django' | \n
field__contains | \nContains (case-sensitive) | \ntitle__contains='Getting Started' | \n
field__icontains | \nContains (case-insensitive) | \ntitle__icontains='django' | \n
field__gte / lte | \nGreater than or equal / Less than or equal | \ncreated_at__gte='2024-01-01' | \n
field__in=[...] | \nIn list | \ncategory__in=[1, 2, 3] | \n
\n\n\nThe double underscore
\n__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__slugmeans traversing the foreign key to the slug field in the Category table.
\n\n
Update the Homepage Template
\n\nAdd category filter navigation and a search box above the article list.
\n\nExample
\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\nFilter Navigation and Search Box Styles
\n\nExample
\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\n\nThe category button links preserve both
\ncategoryandkeywordparameters:?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
Hands-on: Test Filtering and Search
\n\nStart 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
Chapter Summary
\n\nIn 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.
Category filtering and search functionality allows users to quickly find articles of interest.
YouTip