feat(sets): added notes field to sets. Displayed both at top of page, if not empty and in the metadata section, where it can be changed

This commit is contained in:
2025-12-25 20:44:40 -05:00
parent dcf9496db9
commit 5725872060
8 changed files with 90 additions and 0 deletions
+34
View File
@@ -30,6 +30,7 @@ class BrickSet(RebrickableSet):
insert_query: str = 'set/insert'
update_purchase_date_query: str = 'set/update/purchase_date'
update_purchase_price_query: str = 'set/update/purchase_price'
update_description_query: str = 'set/update/description'
# Delete a set
def delete(self, /) -> None:
@@ -370,3 +371,36 @@ class BrickSet(RebrickableSet):
# Update purchase price url
def url_for_purchase_price(self, /) -> str:
return url_for('set.update_purchase_price', id=self.fields.id)
# Update description
def update_description(self, json: Any | None, /) -> Any:
value = json.get('value', None) # type: ignore
if value == '':
value = None
self.fields.description = value
rows, _ = BrickSQL().execute_and_commit(
self.update_description_query,
parameters=self.sql_parameters()
)
if rows != 1:
raise DatabaseException('Could not update the description for set {set} ({id})'.format( # noqa: E501
set=self.fields.set,
id=self.fields.id,
))
# Info
logger.info('Description changed to "{value}" for set {set} ({id})'.format( # noqa: E501
value=value,
set=self.fields.set,
id=self.fields.id,
))
return value
# Update description url
def url_for_description(self, /) -> str:
return url_for('set.update_description', id=self.fields.id)
+1
View File
@@ -4,6 +4,7 @@ SELECT
"bricktracker_sets"."purchase_date",
"bricktracker_sets"."purchase_location",
"bricktracker_sets"."purchase_price",
"bricktracker_sets"."description",
"rebrickable_sets"."set",
"rebrickable_sets"."number",
"rebrickable_sets"."version",
@@ -0,0 +1,3 @@
UPDATE "bricktracker_sets"
SET "description" = :description
WHERE "bricktracker_sets"."id" IS NOT DISTINCT FROM :id
+12
View File
@@ -158,6 +158,18 @@ def update_purchase_price(*, id: str) -> Response:
return jsonify({'value': value})
# Change the value of description
@set_page.route('/<id>/description', methods=['POST'])
@login_required
@exception_handler(__file__, json=True)
def update_description(*, id: str) -> Response:
brickset = BrickSet().select_light(id)
value = brickset.update_description(request.json)
return jsonify({'value': value})
# Change the state of a owner
@set_page.route('/<id>/owner/<metadata_id>', methods=['POST'])
@login_required
+6
View File
@@ -38,6 +38,11 @@ class BrickChanger {
listener = "change";
break;
case "TEXTAREA":
this.html_type = "textarea";
listener = "change";
break;
default:
throw Error(`Unsupported HTML tag type for BrickChanger: ${this.html_element.tagName}`);
}
@@ -131,6 +136,7 @@ class BrickChanger {
case "text":
case "select":
case "textarea":
value = this.html_element.value;
break;
+24
View File
@@ -65,6 +65,30 @@
{% endif %}
{% endmacro %}
{% macro textarea(name, id, prefix, url, value, all=none, read_only=none, icon=none, rows=3, delete=false) %}
{% if all or read_only %}
{{ value }}
{% else %}
<label class="visually-hidden" for="{{ prefix }}-{{ id }}">{{ name }}</label>
<div class="input-group">
{% if icon %}<span class="input-group-text px-1"><i class="ri-{{ icon }} me-1"></i><span class="ms-1 d-none d-md-inline"> {{ name }}</span></span>{% endif %}
<textarea class="form-control form-control-sm flex-shrink-1 px-1" id="{{ prefix }}-{{ id }}" rows="{{ rows }}"
{% if g.login.is_authenticated() and not delete %}
data-changer-id="{{ id }}" data-changer-prefix="{{ prefix }}" data-changer-url="{{ url }}"
{% else %}
disabled
{% endif %}
autocomplete="off">{% if value %}{{ value }}{% endif %}</textarea>
{% if g.login.is_authenticated() and not delete %}
<span id="status-{{ prefix }}-{{ id }}" class="input-group-text ri-save-line px-1"></span>
<button id="clear-{{ prefix }}-{{ id }}" type="button" class="btn btn-sm btn-light btn-outline-danger border px-1"><i class="ri-eraser-line"></i></button>
{% else %}
<span class="input-group-text ri-prohibited-line px-1"></span>
{% endif %}
</div>
{% endif %}
{% endmacro %}
{% macro filter_toggle(filter_id) %}
<button type="button"
class="btn btn-outline-secondary filter-toggle"
+7
View File
@@ -60,6 +60,13 @@
{# Render badges in configured order based on context (grid view vs detail view) #}
{{ badge.render_ordered_badges(item, brickset_tags, brickset_owners, brickset_storages, brickset_purchase_locations, solo=solo, last=last, context='detail' if solo else 'grid') }}
</div>
{% if item.fields.description %}
<div class="card-body border-bottom-0 {% if not solo %}p-1{% endif %} pt-0"{% if current_viewing %} style="border-color: var(--bs-border-color) !important; border-width: 1px !important;"{% endif %}>
<div class="alert alert-info mb-0 {% if not solo %}p-1 small{% endif %}" role="alert">
<i class="ri-sticky-note-line me-1"></i>{{ item.fields.description }}
</div>
</div>
{% endif %}
{% if not tiny and brickset_statuses | length %}
<ul class="list-group list-group-flush card-check border-bottom-0"{% if current_viewing %} style="border-color: var(--bs-border-color) !important; border-width: 1px !important;"{% endif %}>
{% for status in brickset_statuses %}
+3
View File
@@ -34,6 +34,9 @@
<hr>
<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 set purchase locations</a>
{{ accordion.footer() }}
{{ accordion.header('Notes', 'notes', 'set-management', icon='sticky-note-line') }}
{{ form.textarea('Notes', item.fields.id, 'description', item.url_for_description(), item.fields.description, rows=4, delete=delete) }}
{{ accordion.footer() }}
{{ accordion.header('Storage', 'storage', 'set-management', icon='archive-2-line') }}
{% if brickset_storages | length %}
{{ form.select('Storage', item.fields.id, brickset_storages.as_prefix(), brickset_storages.url_for_set_value(item.fields.id), item.fields.storage, brickset_storages, icon='building-line', delete=delete) }}