feat(parts): Added option to hide spare parts but still save them to db

This commit is contained in:
FrederikBaerentsen
2025-12-07 20:41:13 +01:00
parent d6d0a70116
commit 7369d0babf
8 changed files with 74 additions and 12 deletions

View File

@@ -387,10 +387,14 @@
# Default: true
# BK_SHOW_SETS_DUPLICATE_FILTER=true
# Optional: Skip saving or displaying spare parts
# Optional: Skip importing spare parts when downloading sets from Rebrickable
# Default: false
# BK_SKIP_SPARE_PARTS=true
# Optional: Hide spare parts from parts lists (spare parts must still be in database)
# Default: false
# BK_HIDE_SPARE_PARTS=true
# Optional: Namespace of the Socket.IO socket
# Default: bricksocket
# BK_SOCKET_NAMESPACE=customsocket

View File

@@ -35,6 +35,23 @@ See [Migration Guide](docs/migration_guide.md) for detailed instructions
#### Features
- Add spare parts control options
- `BK_SKIP_SPARE_PARTS`: Skip importing spare parts when downloading sets from Rebrickable (parts not saved to database)
- `BK_HIDE_SPARE_PARTS`: Hide spare parts from all parts lists (parts must still be in database)
- Both options are live-changeable in admin configuration panel
- Options can be used independently or together for flexible spare parts management
- Affects all parts displays: /parts page, set details accordion, minifigure parts, and problem parts
- Improved WebSocket/Socket.IO reliability for mobile devices
- Changed connection strategy to polling-first with automatic WebSocket upgrade
- Increased connection timeout to 30 seconds for slow mobile networks
- Added ping/pong keepalive settings (30s timeout, 25s interval)
- Fixed disconnect handler to properly accept optional reason parameter
- Improved server-side connection logging with user agent and transport details
- Fixed environment variable lock detection in admin configuration panel
- Resolved bug where all variables appeared "locked" after saving live settings
- Lock detection now correctly identifies only Docker environment variables set before .env loading
- Variables set via Docker's `environment:` directive remain properly locked
- Variables from data/.env or root .env are correctly shown as editable
- Add individual pagination control system per entity type
- `BK_SETS_SERVER_SIDE_PAGINATION`: Enable/disable pagination for sets
- `BK_PARTS_SERVER_SIDE_PAGINATION`: Enable/disable pagination for parts

View File

@@ -85,6 +85,7 @@ CONFIG: Final[list[dict[str, Any]]] = [
{'n': 'SHOW_GRID_SORT', 'c': bool},
{'n': 'SHOW_SETS_DUPLICATE_FILTER', 'd': True, 'c': bool},
{'n': 'SKIP_SPARE_PARTS', 'c': bool},
{'n': 'HIDE_SPARE_PARTS', 'c': bool},
{'n': 'SOCKET_NAMESPACE', 'd': 'bricksocket'},
{'n': 'SOCKET_PATH', 'd': '/bricksocket/'},
{'n': 'STORAGE_DEFAULT_ORDER', 'd': '"bricktracker_metadata_storages"."name" ASC'}, # noqa: E501

View File

@@ -46,6 +46,7 @@ LIVE_CHANGEABLE_VARS: Final[List[str]] = [
'BK_SHOW_GRID_SORT',
'BK_SHOW_SETS_DUPLICATE_FILTER',
'BK_SKIP_SPARE_PARTS',
'BK_HIDE_SPARE_PARTS',
'BK_USE_REMOTE_IMAGES',
'BK_PEERON_DOWNLOAD_DELAY',
'BK_PEERON_MIN_IMAGE_SIZE',
@@ -317,7 +318,8 @@ class ConfigManager:
'BK_SETS_CONSOLIDATION': 'Enable set consolidation/grouping functionality',
'BK_SHOW_GRID_FILTERS': 'Show filter options on grids by default',
'BK_SHOW_GRID_SORT': 'Show sort options on grids by default',
'BK_SKIP_SPARE_PARTS': 'Skip spare parts when importing sets',
'BK_SKIP_SPARE_PARTS': 'Skip importing spare parts when downloading sets from Rebrickable',
'BK_HIDE_SPARE_PARTS': 'Hide spare parts from parts lists (spare parts must still be in database)',
'BK_USE_REMOTE_IMAGES': 'Use remote images from Rebrickable CDN instead of local',
'BK_STATISTICS_SHOW_CHARTS': 'Show collection growth charts on statistics page',
'BK_STATISTICS_DEFAULT_EXPANDED': 'Expand all statistics sections by default'

View File

@@ -73,7 +73,8 @@ class BrickPartList(BrickRecordList[BrickPart]):
# Prepare context for query
context = {}
if current_app.config.get('SKIP_SPARE_PARTS', False):
# Hide spare parts from display if configured
if current_app.config.get('HIDE_SPARE_PARTS', False):
context['skip_spare_parts'] = True
if theme_id and theme_id != 'all':
context['theme_id'] = theme_id
@@ -114,7 +115,8 @@ class BrickPartList(BrickRecordList[BrickPart]):
filter_context['year'] = year
if search_query:
filter_context['search_query'] = search_query
if current_app.config.get('SKIP_SPARE_PARTS', False):
# Hide spare parts from display if configured
if current_app.config.get('HIDE_SPARE_PARTS', False):
filter_context['skip_spare_parts'] = True
# Field mapping for sorting
@@ -203,8 +205,13 @@ class BrickPartList(BrickRecordList[BrickPart]):
self.brickset = brickset
self.minifigure = minifigure
# Prepare context for hiding spare parts if configured
context = {}
if current_app.config.get('HIDE_SPARE_PARTS', False):
context['skip_spare_parts'] = True
# Load the parts from the database
self.list()
self.list(**context)
return self
@@ -217,8 +224,13 @@ class BrickPartList(BrickRecordList[BrickPart]):
# Save the minifigure
self.minifigure = minifigure
# Prepare context for hiding spare parts if configured
context = {}
if current_app.config.get('HIDE_SPARE_PARTS', False):
context['skip_spare_parts'] = True
# Load the parts from the database
self.list(override_query=self.minifigure_query)
self.list(override_query=self.minifigure_query, **context)
return self
@@ -269,7 +281,8 @@ class BrickPartList(BrickRecordList[BrickPart]):
context['storage_id'] = storage_id
if tag_id and tag_id != 'all':
context['tag_id'] = tag_id
if current_app.config.get('SKIP_SPARE_PARTS', False):
# Hide spare parts from display if configured
if current_app.config.get('HIDE_SPARE_PARTS', False):
context['skip_spare_parts'] = True
# Load the problematic parts from the database
@@ -307,7 +320,8 @@ class BrickPartList(BrickRecordList[BrickPart]):
filter_context['tag_id'] = tag_id
if search_query:
filter_context['search_query'] = search_query
if current_app.config.get('SKIP_SPARE_PARTS', False):
# Hide spare parts from display if configured
if current_app.config.get('HIDE_SPARE_PARTS', False):
filter_context['skip_spare_parts'] = True
# Field mapping for sorting
@@ -407,7 +421,13 @@ class BrickPartList(BrickRecordList[BrickPart]):
# Process each part
number_of_parts: int = 0
skip_spares = current_app.config.get('SKIP_SPARE_PARTS', False)
for part in inventory:
# Skip spare parts if configured
if skip_spares and part.fields.spare:
continue
# Count the number of parts for minifigures
if minifigure is not None:
number_of_parts += part.fields.quantity

View File

@@ -10,7 +10,12 @@ SUM("bricktracker_parts"."damaged") AS "total_damaged",
{% endblock %}
{% block where %}
WHERE "bricktracker_parts"."figure" IS NOT DISTINCT FROM :figure
{% set conditions = [] %}
{% set _ = conditions.append('"bricktracker_parts"."figure" IS NOT DISTINCT FROM :figure') %}
{% if skip_spare_parts %}
{% set _ = conditions.append('"bricktracker_parts"."spare" = 0') %}
{% endif %}
WHERE {{ conditions | join(' AND ') }}
{% endblock %}
{% block group %}

View File

@@ -10,6 +10,11 @@ IFNULL("bricktracker_parts"."damaged", 0) AS "total_damaged",
{% endblock %}
{% block where %}
WHERE "bricktracker_parts"."id" IS NOT DISTINCT FROM :id
AND "bricktracker_parts"."figure" IS NOT DISTINCT FROM :figure
{% set conditions = [] %}
{% set _ = conditions.append('"bricktracker_parts"."id" IS NOT DISTINCT FROM :id') %}
{% set _ = conditions.append('"bricktracker_parts"."figure" IS NOT DISTINCT FROM :figure') %}
{% if skip_spare_parts %}
{% set _ = conditions.append('"bricktracker_parts"."spare" = 0') %}
{% endif %}
WHERE {{ conditions | join(' AND ') }}
{% endblock %}

View File

@@ -471,7 +471,15 @@
<input class="form-check-input config-toggle" type="checkbox" id="BK_SKIP_SPARE_PARTS" data-var="BK_SKIP_SPARE_PARTS" {{ is_locked('BK_SKIP_SPARE_PARTS') }}>
<label class="form-check-label" for="BK_SKIP_SPARE_PARTS">
BK_SKIP_SPARE_PARTS {{ config_badges('BK_SKIP_SPARE_PARTS') }}
<div class="text-muted small">Skip spare parts when importing sets</div>
<div class="text-muted small">Skip importing spare parts when downloading sets from Rebrickable</div>
</label>
</div>
<div class="form-check form-switch">
<input class="form-check-input config-toggle" type="checkbox" id="BK_HIDE_SPARE_PARTS" data-var="BK_HIDE_SPARE_PARTS" {{ is_locked('BK_HIDE_SPARE_PARTS') }}>
<label class="form-check-label" for="BK_HIDE_SPARE_PARTS">
BK_HIDE_SPARE_PARTS {{ config_badges('BK_HIDE_SPARE_PARTS') }}
<div class="text-muted small">Hide spare parts from parts lists (spare parts must still be in database)</div>
</label>
</div>
</div>