forked from FrederikBaerentsen/BrickTracker
Fixed filtering on /sets page.
This commit is contained in:
+155
-37
@@ -105,17 +105,16 @@ class BrickSetList(BrickRecordList[BrickSet]):
|
||||
# Choose query based on whether filters are applied
|
||||
query_to_use = 'set/list/all_filtered' if has_filters else self.all_query
|
||||
|
||||
# Handle instructions filtering separately (post-SQL filtering)
|
||||
instructions_filter = None
|
||||
# Handle instructions filtering
|
||||
if status_filter in ['has-missing-instructions', '-has-missing-instructions']:
|
||||
instructions_filter = status_filter
|
||||
# Remove from SQL context to avoid SQL errors
|
||||
filter_context['status_filter'] = None
|
||||
# Recalculate has_filters without instructions
|
||||
has_filters = any([theme_id_filter, owner_filter, purchase_location_filter, storage_filter, tag_filter])
|
||||
query_to_use = 'set/list/all_filtered' if has_filters else self.all_query
|
||||
# For instructions filter, we need to load all sets first, then filter and paginate
|
||||
return self._all_filtered_paginated_with_instructions(
|
||||
search_query, page, per_page, sort_field, sort_order,
|
||||
status_filter, theme_id_filter, owner_filter,
|
||||
purchase_location_filter, storage_filter, tag_filter
|
||||
)
|
||||
|
||||
# Use the base pagination method with custom list method
|
||||
# Normal SQL-based filtering and pagination
|
||||
result, total_count = self.paginate(
|
||||
page=page,
|
||||
per_page=per_page,
|
||||
@@ -126,12 +125,8 @@ class BrickSetList(BrickRecordList[BrickSet]):
|
||||
**filter_context
|
||||
)
|
||||
|
||||
# Apply instructions filtering after SQL query
|
||||
if instructions_filter:
|
||||
result, total_count = self._filter_by_instructions(result, total_count, instructions_filter, page, per_page)
|
||||
|
||||
# Populate themes for filter dropdown (always needed)
|
||||
result._populate_themes()
|
||||
# Populate themes for filter dropdown from ALL sets, not just current page
|
||||
result._populate_themes_global()
|
||||
|
||||
return result, total_count
|
||||
|
||||
@@ -157,42 +152,165 @@ class BrickSetList(BrickRecordList[BrickSet]):
|
||||
# If themes can't be loaded, return None to disable theme filtering
|
||||
return None
|
||||
|
||||
def _filter_by_instructions(self, result_list: Self, total_count: int, instructions_filter: str, page: int, per_page: int) -> tuple[Self, int]:
|
||||
"""Filter sets by instruction file existence (post-SQL filtering)"""
|
||||
def _all_filtered_paginated_with_instructions(
|
||||
self,
|
||||
search_query: str | None,
|
||||
page: int,
|
||||
per_page: int,
|
||||
sort_field: str | None,
|
||||
sort_order: str,
|
||||
status_filter: str,
|
||||
theme_id_filter: str | None,
|
||||
owner_filter: str | None,
|
||||
purchase_location_filter: str | None,
|
||||
storage_filter: str | None,
|
||||
tag_filter: str | None
|
||||
) -> tuple[Self, int]:
|
||||
"""Handle filtering when instructions filter is involved"""
|
||||
try:
|
||||
# Load all sets first (without pagination) with full metadata
|
||||
all_sets = BrickSetList()
|
||||
filter_context = {
|
||||
'owners': BrickSetOwnerList.as_columns(),
|
||||
'statuses': BrickSetStatusList.as_columns(),
|
||||
'tags': BrickSetTagList.as_columns(),
|
||||
}
|
||||
all_sets.list(do_theme=True, **filter_context)
|
||||
|
||||
# Load instructions list
|
||||
instructions_list = BrickInstructionsList()
|
||||
instruction_sets = set(instructions_list.sets.keys())
|
||||
|
||||
# Filter the records
|
||||
# Apply all filters manually
|
||||
filtered_records = []
|
||||
for record in result_list.records:
|
||||
for record in all_sets.records:
|
||||
# Apply instructions filter
|
||||
set_id = record.fields.set
|
||||
has_instructions = set_id in instruction_sets
|
||||
|
||||
if instructions_filter == 'has-missing-instructions':
|
||||
# Show sets that are MISSING instructions
|
||||
if not has_instructions:
|
||||
filtered_records.append(record)
|
||||
elif instructions_filter == '-has-missing-instructions':
|
||||
# Show sets that HAVE instructions
|
||||
if has_instructions:
|
||||
filtered_records.append(record)
|
||||
if status_filter == 'has-missing-instructions' and has_instructions:
|
||||
continue # Skip sets that have instructions
|
||||
elif status_filter == '-has-missing-instructions' and not has_instructions:
|
||||
continue # Skip sets that don't have instructions
|
||||
|
||||
# Create new result with filtered records
|
||||
new_result = BrickSetList()
|
||||
new_result.records = filtered_records
|
||||
# Apply other filters manually
|
||||
if search_query and not self._matches_search(record, search_query):
|
||||
continue
|
||||
if theme_id_filter and not self._matches_theme(record, theme_id_filter):
|
||||
continue
|
||||
if owner_filter and not self._matches_owner(record, owner_filter):
|
||||
continue
|
||||
if purchase_location_filter and not self._matches_purchase_location(record, purchase_location_filter):
|
||||
continue
|
||||
if storage_filter and not self._matches_storage(record, storage_filter):
|
||||
continue
|
||||
if tag_filter and not self._matches_tag(record, tag_filter):
|
||||
continue
|
||||
|
||||
# Note: This breaks proper pagination since we're filtering after SQL
|
||||
# The total_count and pagination will be approximate
|
||||
# For proper pagination, we'd need a database table for instructions
|
||||
# This will be implemented in future versions
|
||||
filtered_records.append(record)
|
||||
|
||||
return new_result, len(filtered_records)
|
||||
# Apply sorting
|
||||
if sort_field:
|
||||
filtered_records = self._sort_records(filtered_records, sort_field, sort_order)
|
||||
|
||||
# Calculate pagination
|
||||
total_count = len(filtered_records)
|
||||
start_index = (page - 1) * per_page
|
||||
end_index = start_index + per_page
|
||||
paginated_records = filtered_records[start_index:end_index]
|
||||
|
||||
# Create result
|
||||
result = BrickSetList()
|
||||
result.records = paginated_records
|
||||
|
||||
# Copy themes from the source that has all sets
|
||||
result.themes = all_sets.themes if hasattr(all_sets, 'themes') else []
|
||||
|
||||
# If themes weren't populated, populate them globally
|
||||
if not result.themes:
|
||||
result._populate_themes_global()
|
||||
|
||||
return result, total_count
|
||||
|
||||
except Exception:
|
||||
# If instructions can't be loaded, return original results
|
||||
return result_list, total_count
|
||||
# Fall back to normal pagination without instructions filter
|
||||
return self.all_filtered_paginated(
|
||||
search_query, page, per_page, sort_field, sort_order,
|
||||
None, theme_id_filter, owner_filter,
|
||||
purchase_location_filter, storage_filter, tag_filter
|
||||
)
|
||||
|
||||
def _populate_themes_global(self) -> None:
|
||||
"""Populate themes list from ALL sets, not just current page"""
|
||||
try:
|
||||
# Load all sets to get all possible themes
|
||||
all_sets = BrickSetList().all()
|
||||
themes = set()
|
||||
for record in all_sets.records:
|
||||
if hasattr(record, 'theme') and hasattr(record.theme, 'name'):
|
||||
themes.add(record.theme.name)
|
||||
|
||||
self.themes = list(themes)
|
||||
self.themes.sort()
|
||||
except Exception:
|
||||
# Fall back to current page themes
|
||||
self._populate_themes()
|
||||
|
||||
def _matches_search(self, record, search_query: str) -> bool:
|
||||
"""Check if record matches search query"""
|
||||
search_lower = search_query.lower()
|
||||
return (search_lower in record.fields.name.lower() or
|
||||
search_lower in record.fields.set.lower())
|
||||
|
||||
def _matches_theme(self, record, theme_id: str) -> bool:
|
||||
"""Check if record matches theme filter"""
|
||||
return str(record.fields.theme_id) == theme_id
|
||||
|
||||
def _matches_owner(self, record, owner_filter: str) -> bool:
|
||||
"""Check if record matches owner filter"""
|
||||
if not owner_filter.startswith('owner-'):
|
||||
return True
|
||||
|
||||
# Convert owner-uuid format to owner_uuid column name
|
||||
owner_column = owner_filter.replace('-', '_')
|
||||
|
||||
# Check if record has this owner attribute set to 1
|
||||
return hasattr(record.fields, owner_column) and getattr(record.fields, owner_column) == 1
|
||||
|
||||
def _matches_purchase_location(self, record, location_filter: str) -> bool:
|
||||
"""Check if record matches purchase location filter"""
|
||||
return record.fields.purchase_location == location_filter
|
||||
|
||||
def _matches_storage(self, record, storage_filter: str) -> bool:
|
||||
"""Check if record matches storage filter"""
|
||||
return record.fields.storage == storage_filter
|
||||
|
||||
def _matches_tag(self, record, tag_filter: str) -> bool:
|
||||
"""Check if record matches tag filter"""
|
||||
if not tag_filter.startswith('tag-'):
|
||||
return True
|
||||
|
||||
# Convert tag-uuid format to tag_uuid column name
|
||||
tag_column = tag_filter.replace('-', '_')
|
||||
|
||||
# Check if record has this tag attribute set to 1
|
||||
return hasattr(record.fields, tag_column) and getattr(record.fields, tag_column) == 1
|
||||
|
||||
def _sort_records(self, records, sort_field: str, sort_order: str):
|
||||
"""Sort records manually"""
|
||||
reverse = sort_order == 'desc'
|
||||
|
||||
if sort_field == 'set':
|
||||
return sorted(records, key=lambda r: r.fields.set, reverse=reverse)
|
||||
elif sort_field == 'name':
|
||||
return sorted(records, key=lambda r: r.fields.name, reverse=reverse)
|
||||
elif sort_field == 'year':
|
||||
return sorted(records, key=lambda r: r.fields.year, reverse=reverse)
|
||||
elif sort_field == 'parts':
|
||||
return sorted(records, key=lambda r: r.fields.number_of_parts, reverse=reverse)
|
||||
# Add more sort fields as needed
|
||||
|
||||
return records
|
||||
|
||||
# Sets with a minifigure part damaged
|
||||
def damaged_minifigure(self, figure: str, /) -> Self:
|
||||
|
||||
Reference in New Issue
Block a user