mirror of
https://gitea.baerentsen.space/FrederikBaerentsen/BrickTracker.git
synced 2026-02-05 07:49:06 -06:00
feat(individual): implement read-only mode for individual minifigures and parts
This commit is contained in:
@@ -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
|
||||
|
||||
@@ -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)
|
||||
)
|
||||
|
||||
|
||||
@@ -99,6 +99,8 @@ def list() -> str:
|
||||
@minifigure_page.route('/<figure>/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)
|
||||
)
|
||||
|
||||
@@ -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)
|
||||
)
|
||||
|
||||
@@ -6,17 +6,17 @@
|
||||
|
||||
{% block main %}
|
||||
<div class="container">
|
||||
{% 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'])) %}
|
||||
<div class="row g-3 mb-3">
|
||||
{% if not config['HIDE_ADD_BULK_SET'] %}
|
||||
<div class="col-12 {% if not config['HIDE_INDIVIDUAL_PARTS'] %}col-md-6{% endif %}">
|
||||
<div class="col-12 {% if not config['HIDE_INDIVIDUAL_PARTS'] and not config['DISABLE_INDIVIDUAL_PARTS'] %}col-md-6{% endif %}">
|
||||
<div class="alert alert-primary mb-0 h-100" role="alert">
|
||||
<h4 class="alert-heading">Too many to add?</h4>
|
||||
<p class="mb-0">You can import multiple sets at once with <a href="{{ url_for('add.bulk') }}" class="btn btn-primary"><i class="ri-function-add-line"></i> Bulk add</a>.</p>
|
||||
</div>
|
||||
</div>
|
||||
{% endif %}
|
||||
{% if not config['HIDE_INDIVIDUAL_PARTS'] %}
|
||||
{% if not config['HIDE_INDIVIDUAL_PARTS'] and not config['DISABLE_INDIVIDUAL_PARTS'] %}
|
||||
<div class="col-12 {% if not config['HIDE_ADD_BULK_SET'] %}col-md-6{% endif %}">
|
||||
<div class="alert alert-info mb-0 h-100" role="alert">
|
||||
<h4 class="alert-heading">Adding individual parts?</h4>
|
||||
|
||||
@@ -31,7 +31,7 @@
|
||||
{% if item.endpoint == 'add.add' %}
|
||||
{# Add menu item with optional dropdown for Bulk/Parts #}
|
||||
{% set has_bulk = not config['HIDE_ADD_BULK_SET'] %}
|
||||
{% set has_parts = not config['HIDE_INDIVIDUAL_PARTS'] %}
|
||||
{% set has_parts = not config['HIDE_INDIVIDUAL_PARTS'] and not config['DISABLE_INDIVIDUAL_PARTS'] %}
|
||||
{% if has_bulk or has_parts %}
|
||||
<li class="nav-item dropdown px-1">
|
||||
<a {% if request.url_rule.endpoint in ['add.add', 'add.bulk', 'add.parts'] %}class="nav-link active" aria-current="page"{% else %}class="nav-link"{% endif %}
|
||||
|
||||
@@ -19,14 +19,22 @@
|
||||
</div>
|
||||
{% if g.login.is_authenticated() %}
|
||||
<div class="card-footer p-1 border-bottom">
|
||||
{{ form.input('Qty', item.fields.id, 'quantity', item.url_for_quantity(), item.fields.quantity, icon='functions') }}
|
||||
{% if writes_disabled %}
|
||||
<div class="input-group">
|
||||
<span class="input-group-text px-1"><i class="ri-functions me-1"></i><span class="ms-1 d-none d-md-inline"> Qty</span></span>
|
||||
<input class="form-control form-control-sm flex-shrink-1 px-1" type="text" value="{{ item.fields.quantity }}" disabled autocomplete="off">
|
||||
<span class="input-group-text ri-prohibited-line px-1"></span>
|
||||
</div>
|
||||
{% else %}
|
||||
{{ form.input('Qty', item.fields.id, 'quantity', item.url_for_quantity(), item.fields.quantity, icon='functions') }}
|
||||
{% endif %}
|
||||
</div>
|
||||
{% endif %}
|
||||
{% if brickset_statuses | length %}
|
||||
<ul class="list-group list-group-flush card-check border-bottom-0">
|
||||
{% for status in brickset_statuses %}
|
||||
<li class="d-flex list-group-item p-1 text-nowrap">
|
||||
{{ form.checkbox(status.fields.name, item.fields.id, status.as_dataset(), status.url_for_individual_minifigure_state(item.fields.id), item.fields[status.as_column()]) }}
|
||||
{{ form.checkbox(status.fields.name, item.fields.id, status.as_dataset(), status.url_for_individual_minifigure_state(item.fields.id), item.fields[status.as_column()], delete=writes_disabled) }}
|
||||
</li>
|
||||
{% endfor %}
|
||||
</ul>
|
||||
|
||||
@@ -26,7 +26,15 @@
|
||||
</div>
|
||||
{% if g.login.is_authenticated() %}
|
||||
<div class="card-footer p-1">
|
||||
{{ form.input('Qty', item.fields.id, 'quantity', item.url_for_quantity(), item.fields.quantity, icon='functions') }}
|
||||
{% if writes_disabled %}
|
||||
<div class="input-group">
|
||||
<span class="input-group-text px-1"><i class="ri-functions me-1"></i><span class="ms-1 d-none d-md-inline"> Qty</span></span>
|
||||
<input class="form-control form-control-sm flex-shrink-1 px-1" type="text" value="{{ item.fields.quantity }}" disabled autocomplete="off">
|
||||
<span class="input-group-text ri-prohibited-line px-1"></span>
|
||||
</div>
|
||||
{% else %}
|
||||
{{ form.input('Qty', item.fields.id, 'quantity', item.url_for_quantity(), item.fields.quantity, icon='functions') }}
|
||||
{% endif %}
|
||||
</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
|
||||
@@ -31,17 +31,25 @@
|
||||
</div>
|
||||
{% endif %}
|
||||
<div class="card-body border-bottom">
|
||||
<div class="row g-2">
|
||||
<div class="col-12 col-lg-4">
|
||||
{{ form.input('Quantity', item.fields.id, 'quantity', item.url_for_quantity(), item.fields.quantity, icon='functions') }}
|
||||
{% if writes_disabled %}
|
||||
<div class="alert alert-secondary mb-0">
|
||||
<i class="ri-functions"></i> Quantity: {{ item.fields.quantity }} |
|
||||
<i class="ri-question-line"></i> Missing: {{ item.fields.missing or 0 }} |
|
||||
<i class="ri-error-warning-line"></i> Damaged: {{ item.fields.damaged or 0 }}
|
||||
</div>
|
||||
<div class="col-12 col-lg-4">
|
||||
{{ form.input('Missing', item.fields.id, 'missing', item.url_for_problem('missing'), item.fields.missing, icon='question-line') }}
|
||||
{% else %}
|
||||
<div class="row g-2">
|
||||
<div class="col-12 col-lg-4">
|
||||
{{ form.input('Quantity', item.fields.id, 'quantity', item.url_for_quantity(), item.fields.quantity, icon='functions') }}
|
||||
</div>
|
||||
<div class="col-12 col-lg-4">
|
||||
{{ form.input('Missing', item.fields.id, 'missing', item.url_for_problem('missing'), item.fields.missing, icon='question-line') }}
|
||||
</div>
|
||||
<div class="col-12 col-lg-4">
|
||||
{{ form.input('Damaged', item.fields.id, 'damaged', item.url_for_problem('damaged'), item.fields.damaged, icon='error-warning-line') }}
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-12 col-lg-4">
|
||||
{{ form.input('Damaged', item.fields.id, 'damaged', item.url_for_problem('damaged'), item.fields.damaged, icon='error-warning-line') }}
|
||||
</div>
|
||||
</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
<div class="accordion accordion-flush border-top" id="individual-part-details-{{ item.fields.id }}">
|
||||
{% 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') }}
|
||||
<a href="{{ url_for('individual_part.delete_part', id=item.fields.id) }}" class="btn btn-danger" role="button" data-bs-toggle="modal" data-bs-target="#deleteModal"><i class="ri-close-line"></i> Delete this individual part instance</a>
|
||||
{{ accordion.footer() }}
|
||||
|
||||
@@ -51,9 +51,9 @@
|
||||
|
||||
{% if solo %}
|
||||
<div class="accordion accordion-flush border-top" id="lot-details">
|
||||
{{ 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') }}
|
||||
<a href="{{ url_for('individual_part.delete_lot', lot_id=item.fields.id) }}" class="btn btn-danger" role="button" data-bs-toggle="modal" data-bs-target="#deleteModal">
|
||||
<i class="ri-delete-bin-line"></i> Delete entire lot and all parts
|
||||
@@ -66,7 +66,7 @@
|
||||
<div class="card-footer"></div>
|
||||
</div>
|
||||
|
||||
{% if solo and g.login.is_authenticated() %}
|
||||
{% if solo and g.login.is_authenticated() and not writes_disabled %}
|
||||
<!-- Delete Confirmation Modal -->
|
||||
<div class="modal fade" id="deleteModal" tabindex="-1" aria-labelledby="deleteModalLabel" aria-hidden="true">
|
||||
<div class="modal-dialog">
|
||||
|
||||
@@ -6,7 +6,14 @@
|
||||
{% if item.__class__.__name__ == 'IndividualPartLot' %}
|
||||
{{ accordion.header('Title', 'accordion-title-' ~ item.fields.id, 'accordion-management-' ~ item.fields.id, icon='file-text-line') }}
|
||||
<div class="alert alert-info" role="alert">Give this part lot a descriptive name to help identify it later. This is shown in the lot card header and list views.</div>
|
||||
{{ form.input('', item.fields.id, 'lot-name-' ~ item.fields.id, url_for('individual_part.update_lot_name', lot_id=item.fields.id), item.fields.name) }}
|
||||
{% if management_read_only %}
|
||||
<div class="input-group">
|
||||
<input class="form-control form-control-sm flex-shrink-1 px-1" type="text" value="{{ item.fields.name or '' }}" disabled autocomplete="off">
|
||||
<span class="input-group-text ri-prohibited-line px-1"></span>
|
||||
</div>
|
||||
{% else %}
|
||||
{{ form.input('', item.fields.id, 'lot-name-' ~ item.fields.id, url_for('individual_part.update_lot_name', lot_id=item.fields.id), item.fields.name) }}
|
||||
{% endif %}
|
||||
{{ accordion.footer() }}
|
||||
{% endif %}
|
||||
{{ accordion.header('Owners', 'accordion-owners-' ~ item.fields.id, 'accordion-management-' ~ item.fields.id, icon='group-line', class='p-0') }}
|
||||
@@ -14,9 +21,9 @@
|
||||
{% if brickset_owners | length %}
|
||||
{% for owner in brickset_owners %}
|
||||
{% if item.__class__.__name__ == 'IndividualPartLot' %}
|
||||
<li class="d-flex list-group-item list-group-item-action text-nowrap">{{ form.checkbox(owner.fields.name, item.fields.id, owner.as_dataset(), url_for('individual_part.update_lot_owner', lot_id=item.fields.id, metadata_id=owner.fields.id), item.fields[owner.as_column()] | default(false)) }}</li>
|
||||
<li class="d-flex list-group-item list-group-item-action text-nowrap">{{ form.checkbox(owner.fields.name, item.fields.id, owner.as_dataset(), url_for('individual_part.update_lot_owner', lot_id=item.fields.id, metadata_id=owner.fields.id), item.fields[owner.as_column()] | default(false), delete=management_read_only) }}</li>
|
||||
{% else %}
|
||||
<li class="d-flex list-group-item list-group-item-action text-nowrap">{{ form.checkbox(owner.fields.name, item.fields.id, owner.as_dataset(), owner.url_for_individual_part_state(item.fields.id), item.fields[owner.as_column()]) }}</li>
|
||||
<li class="d-flex list-group-item list-group-item-action text-nowrap">{{ form.checkbox(owner.fields.name, item.fields.id, owner.as_dataset(), owner.url_for_individual_part_state(item.fields.id), item.fields[owner.as_column()], delete=management_read_only) }}</li>
|
||||
{% endif %}
|
||||
{% endfor %}
|
||||
{% else %}
|
||||
@@ -32,14 +39,31 @@
|
||||
<div class="alert alert-info" role="alert">The expected date format here is <code>yyyy/mm/dd</code> (year/month/day), but you can configured how it is displayed in the part lot card with the <code>PURCHASE_DATE_FORMAT</code> variable.</div>
|
||||
<div class="row row-cols-lg-auto g-1 justify-content-start align-items-center pb-2">
|
||||
<div class="col-12">
|
||||
{{ form.input('Date', item.fields.id, 'purchase_date', url_for('individual_part.update_lot_purchase_date', lot_id=item.fields.id), item.fields.purchase_date or '', date=true, icon='calendar-line') }}
|
||||
{% if management_read_only %}
|
||||
<div class="input-group">
|
||||
<span class="input-group-text px-1"><i class="ri-calendar-line me-1"></i><span class="ms-1 d-none d-md-inline"> Date</span></span>
|
||||
<input class="form-control form-control-sm flex-shrink-1 px-1" type="text" value="{{ item.fields.purchase_date or '' }}" disabled autocomplete="off">
|
||||
<span class="input-group-text ri-prohibited-line px-1"></span>
|
||||
</div>
|
||||
{% else %}
|
||||
{{ form.input('Date', item.fields.id, 'purchase_date', url_for('individual_part.update_lot_purchase_date', lot_id=item.fields.id), item.fields.purchase_date or '', date=true, icon='calendar-line') }}
|
||||
{% endif %}
|
||||
</div>
|
||||
<div class="col-12 flex-grow-1">
|
||||
{{ form.input('Price', item.fields.id, 'purchase_price', url_for('individual_part.update_lot_purchase_price', lot_id=item.fields.id), item.fields.purchase_price or '', suffix=config['PURCHASE_CURRENCY'], icon='wallet-3-line') }}
|
||||
{% if management_read_only %}
|
||||
<div class="input-group">
|
||||
<span class="input-group-text px-1"><i class="ri-wallet-3-line me-1"></i><span class="ms-1 d-none d-md-inline"> Price</span></span>
|
||||
<input class="form-control form-control-sm flex-shrink-1 px-1" type="text" value="{{ item.fields.purchase_price or '' }}" disabled autocomplete="off">
|
||||
<span class="input-group-text d-none d-md-inline px-1">{{ config['PURCHASE_CURRENCY'] }}</span>
|
||||
<span class="input-group-text ri-prohibited-line px-1"></span>
|
||||
</div>
|
||||
{% else %}
|
||||
{{ form.input('Price', item.fields.id, 'purchase_price', url_for('individual_part.update_lot_purchase_price', lot_id=item.fields.id), item.fields.purchase_price or '', suffix=config['PURCHASE_CURRENCY'], icon='wallet-3-line') }}
|
||||
{% endif %}
|
||||
</div>
|
||||
<div class="col-12 flex-grow-1">
|
||||
{% if brickset_purchase_locations | length %}
|
||||
{{ form.select('Location', item.fields.id, brickset_purchase_locations.as_prefix(), url_for('individual_part.update_lot_purchase_location', lot_id=item.fields.id), item.fields.purchase_location, brickset_purchase_locations, icon='building-line') }}
|
||||
{{ form.select('Location', item.fields.id, brickset_purchase_locations.as_prefix(), url_for('individual_part.update_lot_purchase_location', lot_id=item.fields.id), item.fields.purchase_location, brickset_purchase_locations, icon='building-line', delete=management_read_only) }}
|
||||
{% else %}
|
||||
<i class="ri-error-warning-line"></i> No purchase location found.
|
||||
{% endif %}
|
||||
@@ -49,11 +73,11 @@
|
||||
<a href="{{ url_for('admin.admin', open_purchase_location=true) }}" class="btn btn-primary" role="button"><i class="ri-settings-4-line"></i> Manage the part lot purchase locations</a>
|
||||
{{ accordion.footer() }}
|
||||
{{ accordion.header('Notes', 'accordion-notes-' ~ item.fields.id, 'accordion-management-' ~ item.fields.id, icon='sticky-note-line') }}
|
||||
{{ form.textarea('', item.fields.id, 'lot-description-' ~ item.fields.id, url_for('individual_part.update_lot_description', lot_id=item.fields.id), item.fields.description, rows=4) }}
|
||||
{{ form.textarea('', item.fields.id, 'lot-description-' ~ item.fields.id, url_for('individual_part.update_lot_description', lot_id=item.fields.id), item.fields.description, rows=4, delete=management_read_only) }}
|
||||
{{ accordion.footer() }}
|
||||
{{ accordion.header('Storage', 'accordion-storage-' ~ item.fields.id, 'accordion-management-' ~ item.fields.id, icon='archive-2-line') }}
|
||||
{% if brickset_storages | length %}
|
||||
{{ form.select('Storage', item.fields.id, brickset_storages.as_prefix(), url_for('individual_part.update_lot_storage', lot_id=item.fields.id), item.fields.storage, brickset_storages, icon='building-line') }}
|
||||
{{ form.select('Storage', item.fields.id, brickset_storages.as_prefix(), url_for('individual_part.update_lot_storage', lot_id=item.fields.id), item.fields.storage, brickset_storages, icon='building-line', delete=management_read_only) }}
|
||||
{% else %}
|
||||
<p class="text-center"><i class="ri-error-warning-line"></i> No storage found.</p>
|
||||
{% endif %}
|
||||
@@ -63,7 +87,7 @@
|
||||
{% else %}
|
||||
{{ accordion.header('Storage', 'accordion-storage-' ~ item.fields.id, 'accordion-management-' ~ item.fields.id, icon='archive-2-line') }}
|
||||
{% if brickset_storages | length %}
|
||||
{{ form.select('Storage', item.fields.id, brickset_storages.as_prefix(), brickset_storages.url_for_individual_part_value(item.fields.id), item.fields.storage, brickset_storages, icon='building-line') }}
|
||||
{{ form.select('Storage', item.fields.id, brickset_storages.as_prefix(), brickset_storages.url_for_individual_part_value(item.fields.id), item.fields.storage, brickset_storages, icon='building-line', delete=management_read_only) }}
|
||||
{% else %}
|
||||
<p class="text-center"><i class="ri-error-warning-line"></i> No storage found.</p>
|
||||
{% endif %}
|
||||
@@ -74,14 +98,31 @@
|
||||
<div class="alert alert-info" role="alert">The expected date format here is <code>yyyy/mm/dd</code> (year/month/day), but you can configured how it is displayed in the part card with the <code>PURCHASE_DATE_FORMAT</code> variable.</div>
|
||||
<div class="row row-cols-lg-auto g-1 justify-content-start align-items-center pb-2">
|
||||
<div class="col-12">
|
||||
{{ form.input('Date', item.fields.id, 'purchase_date', item.url_for_purchase_date(), item.fields.purchase_date or '', date=true, icon='calendar-line') }}
|
||||
{% if management_read_only %}
|
||||
<div class="input-group">
|
||||
<span class="input-group-text px-1"><i class="ri-calendar-line me-1"></i><span class="ms-1 d-none d-md-inline"> Date</span></span>
|
||||
<input class="form-control form-control-sm flex-shrink-1 px-1" type="text" value="{{ item.fields.purchase_date or '' }}" disabled autocomplete="off">
|
||||
<span class="input-group-text ri-prohibited-line px-1"></span>
|
||||
</div>
|
||||
{% else %}
|
||||
{{ form.input('Date', item.fields.id, 'purchase_date', item.url_for_purchase_date(), item.fields.purchase_date or '', date=true, icon='calendar-line') }}
|
||||
{% endif %}
|
||||
</div>
|
||||
<div class="col-12 flex-grow-1">
|
||||
{{ form.input('Price', item.fields.id, 'purchase_price', item.url_for_purchase_price(), item.fields.purchase_price or '', suffix=config['PURCHASE_CURRENCY'], icon='wallet-3-line') }}
|
||||
{% if management_read_only %}
|
||||
<div class="input-group">
|
||||
<span class="input-group-text px-1"><i class="ri-wallet-3-line me-1"></i><span class="ms-1 d-none d-md-inline"> Price</span></span>
|
||||
<input class="form-control form-control-sm flex-shrink-1 px-1" type="text" value="{{ item.fields.purchase_price or '' }}" disabled autocomplete="off">
|
||||
<span class="input-group-text d-none d-md-inline px-1">{{ config['PURCHASE_CURRENCY'] }}</span>
|
||||
<span class="input-group-text ri-prohibited-line px-1"></span>
|
||||
</div>
|
||||
{% else %}
|
||||
{{ form.input('Price', item.fields.id, 'purchase_price', item.url_for_purchase_price(), item.fields.purchase_price or '', suffix=config['PURCHASE_CURRENCY'], icon='wallet-3-line') }}
|
||||
{% endif %}
|
||||
</div>
|
||||
<div class="col-12 flex-grow-1">
|
||||
{% if brickset_purchase_locations | length %}
|
||||
{{ form.select('Location', item.fields.id, brickset_purchase_locations.as_prefix(), brickset_purchase_locations.url_for_individual_part_value(item.fields.id), item.fields.purchase_location, brickset_purchase_locations, icon='building-line') }}
|
||||
{{ form.select('Location', item.fields.id, brickset_purchase_locations.as_prefix(), brickset_purchase_locations.url_for_individual_part_value(item.fields.id), item.fields.purchase_location, brickset_purchase_locations, icon='building-line', delete=management_read_only) }}
|
||||
{% else %}
|
||||
<i class="ri-error-warning-line"></i> No purchase location found.
|
||||
{% endif %}
|
||||
@@ -91,7 +132,7 @@
|
||||
<a href="{{ url_for('admin.admin', open_purchase_location=true) }}" class="btn btn-primary" role="button"><i class="ri-settings-4-line"></i> Manage the purchase locations</a>
|
||||
{{ accordion.footer() }}
|
||||
{{ accordion.header('Notes', 'accordion-notes-' ~ item.fields.id, 'accordion-management-' ~ item.fields.id, icon='sticky-note-line') }}
|
||||
{{ form.textarea('', item.fields.id, 'description', item.url_for_description(), item.fields.description or '', rows=4) }}
|
||||
{{ form.textarea('', item.fields.id, 'description', item.url_for_description(), item.fields.description or '', rows=4, delete=management_read_only) }}
|
||||
{{ accordion.footer() }}
|
||||
{% endif %}
|
||||
{% if item.__class__.__name__ != 'IndividualPartLot' %}
|
||||
@@ -99,7 +140,7 @@
|
||||
<ul class="list-group list-group-flush">
|
||||
{% if brickset_statuses | length %}
|
||||
{% for status in brickset_statuses %}
|
||||
<li class="d-flex list-group-item list-group-item-action text-nowrap">{{ form.checkbox(status.fields.name, item.fields.id, status.as_dataset(), status.url_for_individual_part_state(item.fields.id), item.fields[status.as_column()]) }}</li>
|
||||
<li class="d-flex list-group-item list-group-item-action text-nowrap">{{ form.checkbox(status.fields.name, item.fields.id, status.as_dataset(), status.url_for_individual_part_state(item.fields.id), item.fields[status.as_column()], delete=management_read_only) }}</li>
|
||||
{% endfor %}
|
||||
{% else %}
|
||||
<li class="list-group-item list-group-item-action text-center"><i class="ri-error-warning-line"></i> No status found.</li>
|
||||
@@ -115,9 +156,9 @@
|
||||
{% if brickset_tags | length %}
|
||||
{% for tag in brickset_tags %}
|
||||
{% if item.__class__.__name__ == 'IndividualPartLot' %}
|
||||
<li class="d-flex list-group-item list-group-item-action text-nowrap">{{ form.checkbox(tag.fields.name, item.fields.id, tag.as_dataset(), url_for('individual_part.update_lot_tag', lot_id=item.fields.id, metadata_id=tag.fields.id), item.fields[tag.as_column()] | default(false)) }}</li>
|
||||
<li class="d-flex list-group-item list-group-item-action text-nowrap">{{ form.checkbox(tag.fields.name, item.fields.id, tag.as_dataset(), url_for('individual_part.update_lot_tag', lot_id=item.fields.id, metadata_id=tag.fields.id), item.fields[tag.as_column()] | default(false), delete=management_read_only) }}</li>
|
||||
{% else %}
|
||||
<li class="d-flex list-group-item list-group-item-action text-nowrap">{{ form.checkbox(tag.fields.name, item.fields.id, tag.as_dataset(), tag.url_for_individual_part_state(item.fields.id), item.fields[tag.as_column()]) }}</li>
|
||||
<li class="d-flex list-group-item list-group-item-action text-nowrap">{{ form.checkbox(tag.fields.name, item.fields.id, tag.as_dataset(), tag.url_for_individual_part_state(item.fields.id), item.fields[tag.as_column()], delete=management_read_only) }}</li>
|
||||
{% endif %}
|
||||
{% endfor %}
|
||||
{% else %}
|
||||
|
||||
Reference in New Issue
Block a user