FootyCollect provides comprehensive search and filtering tools to help you find specific items in your collection, whether you have 10 items or 1,000. The search system supports text queries combined with multiple filters for precise results.
Quick Search
The main search bar allows you to search across multiple fields:
Item names : Auto-generated names like “Real Madrid Home 2023-24”
Club names : Search by team name
Player names : Find jerseys by player
Descriptions : Search custom descriptions and notes
Tags : Find items by custom tags
Search is case-insensitive and supports partial matches. Searching for “madrid” will find “Real Madrid”, “Atletico Madrid”, etc.
Filter Options
Combine search queries with filters for precise results:
Item Type Filter
Filter by specific item types:
Jerseys
Shorts
Outerwear
Tracksuits
Pants
Other items
# Filter user's jerseys only
items = BaseItem.objects.filter(
user = user,
item_type = "jersey"
)
Club Filter
Filter by club to see all items from a specific team:
# Get all Real Madrid items
items = item_service.get_items_by_club(user).filter(
club__name__icontains = "Real Madrid"
)
By Club View all items from a specific club organized by season
By Season See all items from a particular season across clubs
By Brand Filter by manufacturer (Nike, Adidas, Puma, etc.)
By Condition Find items in specific condition ranges
Season Filter
Organize items chronologically:
# Get items ordered by season
items = item_service.get_items_by_season(user)
Seasons are stored in “YYYY-YY” format (e.g., “2023-24”) for consistent sorting.
Brand Filter
Filter by manufacturer:
# Find all Nike items
items = items.filter( brand__name = "Nike" )
Condition Filter
Filter by condition rating or detailed condition:
# Find mint condition items (9-10 rating)
items = items.filter( condition__gte = 9 )
# Find BNWT items
items = items.filter( detailed_condition = "BNWT" )
Visibility Filters
Control which items appear in searches:
# Public items only
public_items = BaseItem.objects.public()
# Private items only
private_items = BaseItem.objects.private()
# Draft items only
drafts = BaseItem.objects.drafts()
Public items are visible to all users. Private items only appear in your own collection. Draft items don’t appear anywhere until published.
Advanced Search
The ItemService provides advanced search with multiple filter combinations:
class ItemService :
def search_items_advanced (
self ,
query : str ,
user : User = None ,
filters : dict[ str , Any] | None = None ,
) -> QuerySet[Jersey]:
# Start with user's items or public items
items = (
self .item_repository.get_user_items(user)
if user
else self .item_repository.get_public_items()
)
# Apply text search
if query:
items = self .item_repository.search_items(query, user)
# Apply additional filters
if filters:
items = self ._apply_filters(items, filters)
return items
Filter Dictionary
Pass multiple filters as a dictionary:
filters = {
"brand" : "Nike" ,
"club" : "Barcelona" ,
"condition" : 10 ,
"is_draft" : False ,
"is_private" : False ,
}
results = item_service.search_items_advanced(
query = "Messi" ,
user = request.user,
filters = filters
)
This example finds:
Public, published items (not drafts)
Made by Nike
From Barcelona
In mint condition (10/10)
Containing “Messi” in searchable fields
Database Indexes
FootyCollect uses strategic database indexes for fast search performance:
class Meta :
indexes = [
models.Index( fields = [ "user" ]),
models.Index( fields = [ "item_type" ]),
models.Index( fields = [ "club" ]),
models.Index( fields = [ "brand" ]),
models.Index( fields = [ "created_at" ]),
# Composite indexes for common query patterns
models.Index(
fields = [ "user" , "item_type" ],
name = "baseitem_user_item_type_idx"
),
models.Index(
fields = [ "user" , "is_private" , "is_draft" ],
name = "baseitem_user_visibility_idx"
),
models.Index(
fields = [ "user" , "created_at" ],
name = "baseitem_user_created_idx"
),
models.Index(
fields = [ "club" , "season" ],
name = "baseitem_club_season_idx"
),
]
Composite indexes optimize common query patterns like “find all jerseys for this user” or “find all items from this club in this season”.
Custom Managers
FootyCollect uses custom Django managers for common queries:
BaseItemManager
For the BaseItem model:
class BaseItemManager ( models . Manager ):
def public ( self ):
return self .filter( is_private = False , is_draft = False )
def private ( self ):
return self .filter( is_private = True )
def drafts ( self ):
return self .filter( is_draft = True )
# Usage:
BaseItem.objects.public() # All public items
BaseItem.objects.private() # All private items
BaseItem.objects.drafts() # All drafts
MTIManager
For specific item types (Jersey, Shorts, etc.):
class MTIManager ( models . Manager ):
def public ( self ):
return self .filter(
base_item__is_private = False ,
base_item__is_draft = False
)
def private ( self ):
return self .filter( base_item__is_private = True )
def drafts ( self ):
return self .filter( base_item__is_draft = True )
# Usage:
Jersey.objects.public() # All public jerseys
Shorts.objects.private() # All private shorts
Why separate managers for MTI?
Multi-Table Inheritance requires traversing the relationship to BaseItem for visibility filters. The MTIManager handles this automatically: # Without MTIManager (manual)
Jersey.objects.filter(
base_item__is_private = False ,
base_item__is_draft = False
)
# With MTIManager (clean)
Jersey.objects.public()
This provides a consistent API across BaseItem and specific item types.
Tag-Based Search
FootyCollect supports custom tags for flexible categorization:
# Add tags to items
item.tags.add( "match-worn" , "signed" , "rare" )
# Search by tag
items = BaseItem.objects.filter( tags__name__in = [ "signed" ])
# Find items with multiple tags
items = BaseItem.objects.filter(
tags__name = "signed"
).filter(
tags__name = "rare"
)
Popular Tag Use Cases
match-worn : Items worn in actual matches
signed : Autographed items
rare : Limited edition or hard-to-find items
vintage : Retro or historical items
grail : Dream items or most prized pieces
for-sale : Items available for trade/sale
Sort Options
Sort search results by various criteria:
Date Added (Default)
items = items.order_by( "-created_at" ) # Newest first
By Club Name
items = items.order_by( "club__name" , "-created_at" )
By Season
items = items.order_by( "season__year" , "-created_at" )
By Condition
items = items.order_by( "-condition" ) # Best condition first
By Brand
items = items.order_by( "brand__name" , "-created_at" )
Collection Analytics
Get insights about search results:
summary = item_service.get_user_collection_summary(user)
# Returns:
{
"total_items" : 150 ,
"by_type" : {
"jersey" : 120 ,
"shorts" : 15 ,
"outerwear" : 10 ,
"tracksuit" : 5
},
"by_condition" : {
10 : 30 , # Mint condition
9 : 45 ,
8 : 50 ,
7 : 25
},
"by_brand" : {
"Nike" : 60 ,
"Adidas" : 50 ,
"Puma" : 30 ,
"Umbro" : 10
},
"by_club" : {
"Real Madrid" : 20 ,
"Barcelona" : 18 ,
"Manchester United" : 15
},
"recent_items" : [ < QuerySet > ]
}
Query Optimization
FootyCollect uses select_related and prefetch_related to minimize database queries:
# Fetch items with related data in a single query
items = BaseItem.objects.select_related(
"club" ,
"brand" ,
"season"
).prefetch_related(
"competitions" ,
"tags" ,
"photos"
).filter( user = user)
Large result sets are paginated for performance:
from django.core.paginator import Paginator
paginator = Paginator(items, 20 ) # 20 items per page
page_obj = paginator.get_page(page_number)
Search results are paginated at 20 items per page by default. This keeps page load times fast even with large collections.
Best Practices
Use specific filters : Combine multiple filters to narrow results quickly
Start broad, then narrow : Begin with a general search, then add filters
Use tags strategically : Create a consistent tagging system for your collection
Leverage visibility filters : Keep drafts separate from published items
Sort by relevant criteria : Choose sort order based on what you’re looking for
Search within results : Use filters to refine large search results