diff --git a/CHANGELOG.md b/CHANGELOG.md index 2de69bd..078fe30 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -37,7 +37,9 @@ - Purchase tracking with date, location, and price - Quick navigation from set minifigures to individual instances - Filter and search capabilities - - Feature flags: `BK_HIDE_INDIVIDUAL_MINIFIGURES` (hide UI), `BK_DISABLE_INDIVIDUAL_MINIFIGURES` (block writes) + - Feature flags: + - `BK_HIDE_INDIVIDUAL_MINIFIGURES`: Hides individual minifigures UI elements (navbar menu item, links from minifigure detail pages) + - `BK_DISABLE_INDIVIDUAL_MINIFIGURES`: Enables read-only mode - all individual minifigure pages remain accessible but with all editing fields disabled (quantity, parts table, metadata inputs), delete buttons hidden, and write operations blocked. - **Individual Parts Tracking** - Track loose parts outside of sets and minifigures @@ -46,7 +48,9 @@ - Problem tracking (missing/damaged/checked states) - Purchase tracking with date, location, and price - Bulk part addition interface - - Feature flags: `BK_HIDE_INDIVIDUAL_PARTS` (hide UI), `BK_DISABLE_INDIVIDUAL_PARTS` (block writes) + - Feature flags: + - `BK_HIDE_INDIVIDUAL_PARTS`: Hides individual parts UI elements (navbar menu item, "Add Parts" button, links from part detail pages) + - `BK_DISABLE_INDIVIDUAL_PARTS`: Enables read-only mode - all individual parts and lot pages remain accessible but with all editing fields disabled (quantity, missing/damaged, parts table, metadata inputs), delete buttons hidden, "Add Parts" menu item removed, and write operations blocked. The /add/ page also hides the "Adding individual parts?" section. - **Part Lots System** - Organize individual parts into logical lots/collections diff --git a/bricktracker/views/individual_part.py b/bricktracker/views/individual_part.py index 43a9811..e0a7faf 100644 --- a/bricktracker/views/individual_part.py +++ b/bricktracker/views/individual_part.py @@ -48,9 +48,12 @@ def require_individual_parts_write(f): def list() -> str: parts = IndividualPartList().all() + writes_disabled = current_app.config.get('DISABLE_INDIVIDUAL_PARTS', False) + return render_template( 'individual_parts.html', parts=parts, + writes_disabled=writes_disabled, **set_metadata_lists(as_class=True) ) @@ -181,10 +184,13 @@ def details(*, id: str) -> str: error=e )) + writes_disabled = current_app.config.get('DISABLE_INDIVIDUAL_PARTS', False) + return render_template( 'individual_part/details.html', item=item, lot=lot, + writes_disabled=writes_disabled, **set_metadata_lists(as_class=True) ) @@ -481,10 +487,13 @@ def lot_details(*, lot_id: str) -> str: """Display details for an individual part lot (behaves like a set)""" lot = IndividualPartLot().select_by_id(lot_id) + writes_disabled = current_app.config.get('DISABLE_INDIVIDUAL_PARTS', False) + return render_template( 'individual_part/lot_details.html', item=lot, # Pass as 'item' like sets do solo=True, + writes_disabled=writes_disabled, **set_metadata_lists(as_class=True) ) diff --git a/bricktracker/views/minifigure.py b/bricktracker/views/minifigure.py index f700c67..167dc5b 100644 --- a/bricktracker/views/minifigure.py +++ b/bricktracker/views/minifigure.py @@ -99,6 +99,8 @@ def list() -> str: @minifigure_page.route('/
/details') @exception_handler(__file__) def details(*, figure: str) -> str: + writes_disabled = current_app.config.get('DISABLE_INDIVIDUAL_MINIFIGURES', False) + return render_template( 'minifigure.html', item=BrickMinifigure().select_generic(figure), @@ -106,5 +108,6 @@ def details(*, figure: str) -> str: missing=BrickSetList().missing_minifigure(figure), damaged=BrickSetList().damaged_minifigure(figure), individual_instances=IndividualMinifigureList().instances_by_figure(figure), + writes_disabled=writes_disabled, **set_metadata_lists(as_class=True) ) diff --git a/bricktracker/views/part.py b/bricktracker/views/part.py index 771934e..7d8c1cf 100644 --- a/bricktracker/views/part.py +++ b/bricktracker/views/part.py @@ -1,4 +1,4 @@ -from flask import Blueprint, render_template, request +from flask import Blueprint, current_app, render_template, request from .exceptions import exception_handler from ..individual_part_list import IndividualPartList @@ -201,6 +201,8 @@ def problem() -> str: def details(*, part: str, color: int) -> str: brickpart = BrickPart().select_generic(part, color) + writes_disabled = current_app.config.get('DISABLE_INDIVIDUAL_PARTS', False) + return render_template( 'part.html', item=brickpart, @@ -232,5 +234,6 @@ def details(*, part: str, color: int) -> str: similar_prints=BrickPartList().from_print(brickpart), individual_parts=IndividualPartList().by_part_and_color(part, color), individual_lots=IndividualPartLotList().by_part_and_color(part, color), + writes_disabled=writes_disabled, **set_metadata_lists(as_class=True) ) diff --git a/templates/add.html b/templates/add.html index 0c35113..9eb0499 100644 --- a/templates/add.html +++ b/templates/add.html @@ -6,17 +6,17 @@ {% block main %}
- {% if not bulk and (not config['HIDE_ADD_BULK_SET'] or not config['HIDE_INDIVIDUAL_PARTS']) %} + {% if not bulk and (not config['HIDE_ADD_BULK_SET'] or (not config['HIDE_INDIVIDUAL_PARTS'] and not config['DISABLE_INDIVIDUAL_PARTS'])) %}
{% if not config['HIDE_ADD_BULK_SET'] %} -
+
{% endif %} - {% if not config['HIDE_INDIVIDUAL_PARTS'] %} + {% if not config['HIDE_INDIVIDUAL_PARTS'] and not config['DISABLE_INDIVIDUAL_PARTS'] %}
{% if g.login.is_authenticated() %} {% endif %}
diff --git a/templates/individual_part/details.html b/templates/individual_part/details.html index 4479a26..a7f77b9 100644 --- a/templates/individual_part/details.html +++ b/templates/individual_part/details.html @@ -31,17 +31,25 @@
{% endif %}
-
-
- {{ form.input('Quantity', item.fields.id, 'quantity', item.url_for_quantity(), item.fields.quantity, icon='functions') }} + {% if writes_disabled %} +
+ Quantity: {{ item.fields.quantity }} | + Missing: {{ item.fields.missing or 0 }} | + Damaged: {{ item.fields.damaged or 0 }}
-
- {{ form.input('Missing', item.fields.id, 'missing', item.url_for_problem('missing'), item.fields.missing, icon='question-line') }} + {% else %} +
+
+ {{ form.input('Quantity', item.fields.id, 'quantity', item.url_for_quantity(), item.fields.quantity, icon='functions') }} +
+
+ {{ form.input('Missing', item.fields.id, 'missing', item.url_for_problem('missing'), item.fields.missing, icon='question-line') }} +
+
+ {{ form.input('Damaged', item.fields.id, 'damaged', item.url_for_problem('damaged'), item.fields.damaged, icon='error-warning-line') }} +
-
- {{ form.input('Damaged', item.fields.id, 'damaged', item.url_for_problem('damaged'), item.fields.damaged, icon='error-warning-line') }} -
-
+ {% endif %}
{% if lot %} @@ -61,9 +69,9 @@ {{ accordion.footer() }} {% else %} {# Only show management accordion if NOT part of a lot #} - {% include 'individual_part/management.html' %} + {% set management_read_only = writes_disabled %}{% include 'individual_part/management.html' %} {% endif %} - {% if g.login.is_authenticated() %} + {% if g.login.is_authenticated() and not writes_disabled %} {{ accordion.header('Danger zone', 'accordion-danger-zone-' ~ item.fields.id, 'individual-part-details-' ~ item.fields.id, danger=true, class='text-end') }} Delete this individual part instance {{ accordion.footer() }} diff --git a/templates/individual_part/lot_card.html b/templates/individual_part/lot_card.html index e800f64..08f353d 100644 --- a/templates/individual_part/lot_card.html +++ b/templates/individual_part/lot_card.html @@ -51,9 +51,9 @@ {% if solo %}
- {{ accordion.table(item.parts(), 'Parts', 'parts-inventory', 'lot-details', 'part/lot_table.html', icon='shapes-line', hamburger_menu=g.login.is_authenticated())}} - {% include 'individual_part/management.html' %} - {% if g.login.is_authenticated() %} + {{ accordion.table(item.parts(), 'Parts', 'parts-inventory', 'lot-details', 'part/lot_table.html', icon='shapes-line', hamburger_menu=g.login.is_authenticated(), read_only=writes_disabled)}} + {% set management_read_only = writes_disabled %}{% include 'individual_part/management.html' %} + {% if g.login.is_authenticated() and not writes_disabled %} {{ accordion.header('Danger zone', 'danger-zone', 'lot-details', danger=true, class='text-end') }} Delete entire lot and all parts @@ -66,7 +66,7 @@
-{% if solo and g.login.is_authenticated() %} +{% if solo and g.login.is_authenticated() and not writes_disabled %}