Feature: Product Search & Filtering
Relevant source files
The following files were used as context for generating this wiki page:
The Product Search & Filtering feature is a core component of the Becathlon catalog, enabling users to efficiently browse and discover products. It provides a multi-faceted filtering system that allows users to narrow down the product list based on categories, price range, stock availability, and keyword search. The system is primarily implemented through server-side logic with client-side JavaScript enhancements for a more interactive user experience.
The filtering is handled by a standard HTML form that submits a GET request, reloading the page with the filtered results. This approach is used across the main catalog page, individual category pages, and even on the homepage for a simplified search experience.
System Architecture
The feature operates on a classic client-server model where the user interacts with a filter form, which triggers a GET request to the Django backend. The backend view processes the request parameters, queries the database, and re-renders the product list template with the filtered data.
The following diagram illustrates the high-level data flow for a user applying a filter.
This diagram shows the flow from user interaction on the filter form to the server processing the request and returning a new page with filtered results.
Sources: apps/catalog/views.py:22-143, apps/catalog/templates/catalog/catalog.html:8-74
Backend Implementation
The backend logic resides primarily within the apps/catalog/views.py file, which contains the views responsible for handling filter requests and querying the database.
Sources: apps/catalog/views.py, apps/catalog/forms.py
View Logic: catalog_home and category_view
The main logic is encapsulated in the catalog_home view, which handles requests for the primary catalog page. A similar, slightly more constrained logic exists in category_view for filtering within a specific product category.
The process is as follows:
- Instantiate Form: A
ProductFilterFormis instantiated with therequest.GETdata. This form is used for validation and cleaning the input parameters. - QuerySet Initialization: A base queryset
Product.objects.select_related('product_type').prefetch_related('images').all()is created. - Apply Filters: If the form is valid, filters are applied sequentially to the queryset based on the cleaned data from the form. This includes filtering by categories, price range, stock status, and search keywords.
- Apply Sorting: The
sort_byparameter is used to order the filtered queryset. - Pagination: The final queryset is paginated to display a limited number of products per page.
- Context Rendering: The filtered products, pagination object, form, and other metadata are passed to the template context for rendering.
Sources: apps/catalog/views.py:22-143, apps/catalog/forms.py:5-14
Filter Parameters
The filtering system accepts several GET parameters, which are defined in the ProductFilterForm and processed by the views.
| Parameter | HTML Input(s) | Type | Description |
|---|---|---|---|
categories | input[type="checkbox" name="categories"] | List of Integers | Filters products belonging to the selected ProductType IDs. |
min_price | input[type="number" name="min_price"] | Decimal | Filters products with a price greater than or equal to this value. |
max_price | input[type="number" name="max_price"] | Decimal | Filters products with a price less than or equal to this value. |
in_stock_only | input[type="checkbox" name="in_stock_only"] | Boolean | If true, only shows products with stock > 0. |
search | input[type="text" name="search"] | String | Performs a case-insensitive search on product name and description. |
sort_by | select[name="sort_by"] | String | Determines the sorting order of the results. |
Sources: apps/catalog/templates/catalog/catalog.html:24-65, apps/catalog/forms.py:5-14, apps/catalog/views.py:44-73
Search Logic
The keyword search uses Django's Q objects to perform an OR query across multiple fields. The search term is split into individual words, and the query looks for products where all words appear in either the name or description.
# Source: apps/catalog/views.py:61-68
search_query = cleaned_data.get('search')
if search_query:
search_terms = search_query.split()
query = Q()
for term in search_terms:
query &= (Q(name__icontains=term) | Q(description__icontains=term))
products = products.filter(query)
This snippet demonstrates how a complex search query is constructed from user input to filter products.
Sources: apps/catalog/views.py:61-68
Sorting Logic
The product list can be sorted based on the sort_by parameter. The backend maps predefined string values to specific order_by calls on the queryset.
sort_by Value | ORM Method | Description |
|---|---|---|
newest (default) | .order_by('-created_at') | Newest products first. |
price_low | .order_by('price') | Lowest price first. |
price_high | .order_by('-price') | Highest price first. |
name_asc | .order_by('name') | Name A-Z. |
name_desc | .order_by('-name') | Name Z-A. |
Sources: apps/catalog/views.py:76-88, apps/catalog/templates/catalog/catalog.html:86-92
Frontend Implementation
The frontend consists of the HTML templates that render the filter forms and product grid, and the JavaScript file that adds dynamic behavior to the filter controls.
Sources: apps/catalog/templates/catalog/catalog.html, apps/catalog/static/catalog/js/catalog.js
Filter Form
The main filter interface is a sidebar form with the ID filter-form. It uses the GET method to submit filter criteria.
<!-- Source: apps/catalog/templates/catalog/catalog.html:8-74 -->
<aside class="sidebar">
<div class="sidebar-section">
<h3>Categories</h3>
<form id="filter-form" method="GET">
<!-- Category checkboxes -->
<div class="category-filters scrollable-list">
{% for product_type in product_types %}
<label class="filter-checkbox" data-category-name="{{ product_type.name|lower }}">
<input type="checkbox" name="categories" value="{{ product_type.id }}" ...>
...
</label>
{% endfor %}
</div>
<!-- Price Range inputs -->
<h3>Price Range</h3>
<div class="price-input-group">
<input type="number" id="min_price" name="min_price" ...>
<input type="number" id="max_price" name="max_price" ...>
</div>
<!-- Stock checkbox -->
<h3>Stock</h3>
<label class="filter-checkbox">
<input type="checkbox" name="in_stock_only" value="true" ...>
<span>In Stock Only</span>
</label>
<!-- Search input -->
<h3>Search</h3>
<div class="search-box">
<input type="text" name="search" ...>
</div>
<!-- Action buttons -->
<div class="filter-actions">
<button type="submit" class="btn-apply">Apply Filters</button>
<a href="{% url 'catalog:home' %}" class="btn-clear">Clear All</a>
</div>
</form>
</div>
</aside>
This HTML structure from catalog.html shows the complete filter form with inputs for categories, price, stock, and search.
Sources: apps/catalog/templates/catalog/catalog.html:8-74, apps/catalog/templates/catalog/category.html:15-51, apps/main/templates/main/home.html:200-213
Client-Side Enhancements (catalog.js)
The apps/catalog/static/catalog/js/catalog.js file enhances the user experience of the filter form.
Category Filter Interaction
The initCategoryFilters function provides two key enhancements:
- Collapsible Dropdown: It manages the toggling of the category list.
- Live Category Search: It listens for input on the
#category-search-inputfield and dynamically shows/hides category checkboxes that match the search term, without needing a server request. This improves usability when there are many categories.
The following diagram shows the sequence of events for the live category search.
This diagram illustrates the client-side logic for filtering the category list itself as the user types.
Sources: apps/catalog/static/catalog/js/catalog.js:14-72, apps/catalog/templates/catalog/catalog.html:19-33
Related Feature: Quick View Modal
While not a direct part of filtering, the "Quick View" feature is closely related as it is initiated from the filtered product grid. It allows users to see product details in a modal without navigating away from the catalog page.
Sources: apps/catalog/static/catalog/js/catalog.js:75-195, apps/catalog/views.py:245-277
API Endpoint: Quick View
A dedicated API endpoint provides the data for the modal.
| Endpoint | Method | Description |
|---|---|---|
/catalog/api/product/<int:product_id>/quick-view/ | GET | Returns a JSON object with details for the specified product, including name, price, description, stock status, and images. |
Sources: apps/catalog/views.py:245-277, apps/catalog/static/catalog/js/catalog.js:100-102
Frontend Flow
The initQuickView function in catalog.js handles the modal.
- An event listener is attached to all
.btn-quick-viewbuttons. - On click, it retrieves the
data-product-id. - An
asyncfetchrequest is made to the Quick View API endpoint. - While waiting, a "Loading..." state is shown in the modal.
- Upon receiving a successful JSON response, the
buildQuickViewHTMLfunction constructs the modal's content. - The modal content is updated with the product details.
This diagram shows the asynchronous flow of fetching product data and displaying it in the Quick View modal.
Sources: apps/catalog/static/catalog/js/catalog.js:82-120, apps/catalog/views.py:245-277
Conclusion
The Product Search & Filtering feature in Becathlon is a robust, server-rendered system that effectively utilizes Django's ORM and forms for powerful data filtering. The backend logic is clean and applies filters for categories, price, stock, and keywords in a sequential manner. The frontend is enhanced with JavaScript to improve the usability of the category selection process. The architecture is straightforward, relying on standard web practices (GET requests for filtering), and includes thoughtful UX features like the Quick View modal to create a smooth browsing experience. The code also contains placeholders for future upgrades, such as fully AJAX-driven filtering.