From f34bbe0602dc4e7354ed4aa4d089e90376ac151f Mon Sep 17 00:00:00 2001 From: Gregoo Date: Fri, 31 Jan 2025 18:08:53 +0100 Subject: [PATCH] Set tags --- bricktracker/app.py | 2 + bricktracker/metadata_list.py | 3 +- bricktracker/record_list.py | 2 + bricktracker/reload.py | 5 ++ bricktracker/set.py | 12 ++- bricktracker/set_list.py | 8 +- bricktracker/set_tag.py | 16 ++++ bricktracker/set_tag_list.py | 17 ++++ bricktracker/sql/migrations/0014.sql | 19 +++++ bricktracker/sql/schema/drop.sql | 2 + bricktracker/sql/set/base/base.sql | 3 + bricktracker/sql/set/base/full.sql | 5 ++ bricktracker/sql/set/delete/set.sql | 3 + bricktracker/sql/set/metadata/tag/base.sql | 6 ++ bricktracker/sql/set/metadata/tag/delete.sql | 9 ++ bricktracker/sql/set/metadata/tag/insert.sql | 14 ++++ bricktracker/sql/set/metadata/tag/list.sql | 1 + bricktracker/sql/set/metadata/tag/select.sql | 5 ++ .../sql/set/metadata/tag/update/field.sql | 3 + .../sql/set/metadata/tag/update/state.sql | 10 +++ bricktracker/sql_counter.py | 1 + bricktracker/version.py | 2 +- bricktracker/views/add.py | 4 + bricktracker/views/admin/admin.py | 13 ++- bricktracker/views/admin/tag.py | 84 +++++++++++++++++++ bricktracker/views/index.py | 3 + bricktracker/views/set.py | 19 ++++- static/scripts/socket/set.js | 16 ++++ templates/add.html | 13 +++ templates/admin.html | 3 + templates/admin/tag.html | 42 ++++++++++ templates/admin/tag/delete.html | 19 +++++ templates/macro/badge.html | 13 ++- templates/set/card.html | 11 ++- templates/set/management.html | 50 +++++++---- templates/sets.html | 16 +++- 36 files changed, 424 insertions(+), 30 deletions(-) create mode 100644 bricktracker/set_tag.py create mode 100644 bricktracker/set_tag_list.py create mode 100644 bricktracker/sql/migrations/0014.sql create mode 100644 bricktracker/sql/set/metadata/tag/base.sql create mode 100644 bricktracker/sql/set/metadata/tag/delete.sql create mode 100644 bricktracker/sql/set/metadata/tag/insert.sql create mode 100644 bricktracker/sql/set/metadata/tag/list.sql create mode 100644 bricktracker/sql/set/metadata/tag/select.sql create mode 100644 bricktracker/sql/set/metadata/tag/update/field.sql create mode 100644 bricktracker/sql/set/metadata/tag/update/state.sql create mode 100644 bricktracker/views/admin/tag.py create mode 100644 templates/admin/tag.html create mode 100644 templates/admin/tag/delete.html diff --git a/bricktracker/app.py b/bricktracker/app.py index 15cb9a3..240bc63 100644 --- a/bricktracker/app.py +++ b/bricktracker/app.py @@ -19,6 +19,7 @@ from bricktracker.views.admin.instructions import admin_instructions_page from bricktracker.views.admin.owner import admin_owner_page from bricktracker.views.admin.retired import admin_retired_page from bricktracker.views.admin.status import admin_status_page +from bricktracker.views.admin.tag import admin_tag_page from bricktracker.views.admin.theme import admin_theme_page from bricktracker.views.error import error_404 from bricktracker.views.index import index_page @@ -85,6 +86,7 @@ def setup_app(app: Flask) -> None: app.register_blueprint(admin_retired_page) app.register_blueprint(admin_owner_page) app.register_blueprint(admin_status_page) + app.register_blueprint(admin_tag_page) app.register_blueprint(admin_theme_page) # An helper to make global variables available to the diff --git a/bricktracker/metadata_list.py b/bricktracker/metadata_list.py index bb2e337..b0d42c3 100644 --- a/bricktracker/metadata_list.py +++ b/bricktracker/metadata_list.py @@ -6,10 +6,11 @@ from .fields import BrickRecordFields from .record_list import BrickRecordList from .set_owner import BrickSetOwner from .set_status import BrickSetStatus +from .set_tag import BrickSetTag logger = logging.getLogger(__name__) -T = TypeVar('T', 'BrickSetStatus', 'BrickSetOwner') +T = TypeVar('T', BrickSetStatus, BrickSetOwner, BrickSetTag) # Lego sets metadata list diff --git a/bricktracker/record_list.py b/bricktracker/record_list.py index 8927e71..3de9bf9 100644 --- a/bricktracker/record_list.py +++ b/bricktracker/record_list.py @@ -10,6 +10,7 @@ if TYPE_CHECKING: from .set import BrickSet from .set_owner import BrickSetOwner from .set_status import BrickSetStatus + from .set_tag import BrickSetTag from .wish import BrickWish T = TypeVar( @@ -17,6 +18,7 @@ T = TypeVar( 'BrickSet', 'BrickSetOwner', 'BrickSetStatus', + 'BrickSetTag', 'BrickPart', 'BrickMinifigure', 'BrickWish', diff --git a/bricktracker/reload.py b/bricktracker/reload.py index 73e9e24..16fca2f 100644 --- a/bricktracker/reload.py +++ b/bricktracker/reload.py @@ -4,6 +4,8 @@ from .set_owner import BrickSetOwner from .set_owner_list import BrickSetOwnerList from .set_status import BrickSetStatus from .set_status_list import BrickSetStatusList +from .set_tag import BrickSetTag +from .set_tag_list import BrickSetTagList from .theme_list import BrickThemeList @@ -20,6 +22,9 @@ def reload() -> None: # Reload the set statuses BrickSetStatusList(BrickSetStatus, force=True) + # Reload the set tags + BrickSetTagList(BrickSetTag, force=True) + # Reload retired sets BrickRetiredList(force=True) diff --git a/bricktracker/set.py b/bricktracker/set.py index f4bf1a2..eb2bafb 100644 --- a/bricktracker/set.py +++ b/bricktracker/set.py @@ -13,6 +13,8 @@ from .set_owner import BrickSetOwner from .set_owner_list import BrickSetOwnerList from .set_status import BrickSetStatus from .set_status_list import BrickSetStatusList +from .set_tag import BrickSetTag +from .set_tag_list import BrickSetTagList from .sql import BrickSQL if TYPE_CHECKING: from .socket import BrickSocket @@ -77,6 +79,13 @@ class BrickSet(RebrickableSet): owner = BrickSetOwnerList(BrickSetOwner).get(id) owner.update_set_state(self, state=True) + # Save the tags + tags: list[str] = list(data.get('tags', [])) + + for id in tags: + tag = BrickSetTagList(BrickSetTag).get(id) + tag.update_set_state(self, state=True) + # Commit the transaction to the database socket.auto_progress( message='Set {set}: writing to the database'.format( @@ -172,7 +181,8 @@ class BrickSet(RebrickableSet): # Load from database if not self.select( owners=BrickSetOwnerList(BrickSetOwner).as_columns(), - statuses=BrickSetStatusList(BrickSetStatus).as_columns(all=True) + statuses=BrickSetStatusList(BrickSetStatus).as_columns(all=True), + tags=BrickSetTagList(BrickSetTag).as_columns(), ): raise NotFoundException( 'Set with ID {id} was not found in the database'.format( diff --git a/bricktracker/set_list.py b/bricktracker/set_list.py index e071dc6..54a3cb8 100644 --- a/bricktracker/set_list.py +++ b/bricktracker/set_list.py @@ -7,6 +7,8 @@ from .set_owner import BrickSetOwner from .set_owner_list import BrickSetOwnerList from .set_status import BrickSetStatus from .set_status_list import BrickSetStatusList +from .set_tag import BrickSetTag +from .set_tag_list import BrickSetTagList from .set import BrickSet @@ -41,7 +43,8 @@ class BrickSetList(BrickRecordList[BrickSet]): for record in self.select( order=self.order, owners=BrickSetOwnerList(BrickSetOwner).as_columns(), - statuses=BrickSetStatusList(BrickSetStatus).as_columns() + statuses=BrickSetStatusList(BrickSetStatus).as_columns(), + tags=BrickSetTagList(BrickSetTag).as_columns(), ): brickset = BrickSet(record=record) @@ -78,7 +81,8 @@ class BrickSetList(BrickRecordList[BrickSet]): order=order, limit=limit, owners=BrickSetOwnerList(BrickSetOwner).as_columns(), - statuses=BrickSetStatusList(BrickSetStatus).as_columns() + statuses=BrickSetStatusList(BrickSetStatus).as_columns(), + tags=BrickSetTagList(BrickSetTag).as_columns(), ): brickset = BrickSet(record=record) diff --git a/bricktracker/set_tag.py b/bricktracker/set_tag.py new file mode 100644 index 0000000..6d81c18 --- /dev/null +++ b/bricktracker/set_tag.py @@ -0,0 +1,16 @@ +from .metadata import BrickMetadata + + +# Lego set tag metadata +class BrickSetTag(BrickMetadata): + kind: str = 'tag' + + # Set state endpoint + set_state_endpoint: str = 'set.update_tag' + + # Queries + delete_query: str = 'set/metadata/tag/delete' + insert_query: str = 'set/metadata/tag/insert' + select_query: str = 'set/metadata/tag/select' + update_field_query: str = 'set/metadata/tag/update/field' + update_set_state_query: str = 'set/metadata/tag/update/state' diff --git a/bricktracker/set_tag_list.py b/bricktracker/set_tag_list.py new file mode 100644 index 0000000..92806f2 --- /dev/null +++ b/bricktracker/set_tag_list.py @@ -0,0 +1,17 @@ +import logging + +from .metadata_list import BrickMetadataList +from .set_tag import BrickSetTag + +logger = logging.getLogger(__name__) + + +# Lego sets tag list +class BrickSetTagList(BrickMetadataList[BrickSetTag]): + kind: str = 'set tags' + + # Database table + table: str = 'bricktracker_set_tags' + + # Queries + select_query = 'set/metadata/tag/list' diff --git a/bricktracker/sql/migrations/0014.sql b/bricktracker/sql/migrations/0014.sql new file mode 100644 index 0000000..37c655e --- /dev/null +++ b/bricktracker/sql/migrations/0014.sql @@ -0,0 +1,19 @@ +-- description: Add set tags + +BEGIN TRANSACTION; + +-- Create a table to define each set tags: an id and a name +CREATE TABLE "bricktracker_metadata_tags" ( + "id" TEXT NOT NULL, + "name" TEXT NOT NULL, + PRIMARY KEY("id") +); + +-- Create a table for the set tags +CREATE TABLE "bricktracker_set_tags" ( + "id" TEXT NOT NULL, + PRIMARY KEY("id"), + FOREIGN KEY("id") REFERENCES "bricktracker_sets"("id") +); + +COMMIT; diff --git a/bricktracker/sql/schema/drop.sql b/bricktracker/sql/schema/drop.sql index abc8522..1bab7d6 100644 --- a/bricktracker/sql/schema/drop.sql +++ b/bricktracker/sql/schema/drop.sql @@ -1,12 +1,14 @@ BEGIN transaction; DROP TABLE IF EXISTS "bricktracker_metadata_statuses"; +DROP TABLE IF EXISTS "bricktracker_metadata_tags"; DROP TABLE IF EXISTS "bricktracker_minifigures"; DROP TABLE IF EXISTS "bricktracker_parts"; DROP TABLE IF EXISTS "bricktracker_sets"; DROP TABLE IF EXISTS "bricktracker_set_checkboxes"; DROP TABLE IF EXISTS "bricktracker_set_statuses"; DROP TABLE IF EXISTS "bricktracker_set_storages"; +DROP TABLE IF EXISTS "bricktracker_set_tags"; DROP TABLE IF EXISTS "bricktracker_wishes"; DROP TABLE IF EXISTS "inventory"; DROP TABLE IF EXISTS "inventory_old"; diff --git a/bricktracker/sql/set/base/base.sql b/bricktracker/sql/set/base/base.sql index 940dab9..ffefe95 100644 --- a/bricktracker/sql/set/base/base.sql +++ b/bricktracker/sql/set/base/base.sql @@ -12,6 +12,9 @@ SELECT {% block owners %} {% if owners %}{{ owners }},{% endif %} {% endblock %} + {% block tags %} + {% if tags %}{{ tags }},{% endif %} + {% endblock %} {% block statuses %} {% if statuses %}{{ statuses }},{% endif %} {% endblock %} diff --git a/bricktracker/sql/set/base/full.sql b/bricktracker/sql/set/base/full.sql index 725b56d..271f890 100644 --- a/bricktracker/sql/set/base/full.sql +++ b/bricktracker/sql/set/base/full.sql @@ -23,6 +23,11 @@ LEFT JOIN "bricktracker_set_statuses" ON "bricktracker_sets"."id" IS NOT DISTINCT FROM "bricktracker_set_statuses"."id" {% endif %} +{% if tags %} +LEFT JOIN "bricktracker_set_tags" +ON "bricktracker_sets"."id" IS NOT DISTINCT FROM "bricktracker_set_tags"."id" +{% endif %} + -- LEFT JOIN + SELECT to avoid messing the total LEFT JOIN ( SELECT diff --git a/bricktracker/sql/set/delete/set.sql b/bricktracker/sql/set/delete/set.sql index 2db140d..4eca845 100644 --- a/bricktracker/sql/set/delete/set.sql +++ b/bricktracker/sql/set/delete/set.sql @@ -12,6 +12,9 @@ WHERE "bricktracker_set_owners"."id" IS NOT DISTINCT FROM '{{ id }}'; DELETE FROM "bricktracker_set_statuses" WHERE "bricktracker_set_statuses"."id" IS NOT DISTINCT FROM '{{ id }}'; +DELETE FROM "bricktracker_set_tags" +WHERE "bricktracker_set_tags"."id" IS NOT DISTINCT FROM '{{ id }}'; + DELETE FROM "bricktracker_minifigures" WHERE "bricktracker_minifigures"."id" IS NOT DISTINCT FROM '{{ id }}'; diff --git a/bricktracker/sql/set/metadata/tag/base.sql b/bricktracker/sql/set/metadata/tag/base.sql new file mode 100644 index 0000000..3ec5725 --- /dev/null +++ b/bricktracker/sql/set/metadata/tag/base.sql @@ -0,0 +1,6 @@ +SELECT + "bricktracker_metadata_tags"."id", + "bricktracker_metadata_tags"."name" +FROM "bricktracker_metadata_tags" + +{% block where %}{% endblock %} \ No newline at end of file diff --git a/bricktracker/sql/set/metadata/tag/delete.sql b/bricktracker/sql/set/metadata/tag/delete.sql new file mode 100644 index 0000000..a80bb8f --- /dev/null +++ b/bricktracker/sql/set/metadata/tag/delete.sql @@ -0,0 +1,9 @@ +BEGIN TRANSACTION; + +ALTER TABLE "bricktracker_set_tags" +DROP COLUMN "tag_{{ id }}"; + +DELETE FROM "bricktracker_metadata_tags" +WHERE "bricktracker_metadata_tags"."id" IS NOT DISTINCT FROM '{{ id }}'; + +COMMIT; \ No newline at end of file diff --git a/bricktracker/sql/set/metadata/tag/insert.sql b/bricktracker/sql/set/metadata/tag/insert.sql new file mode 100644 index 0000000..7a62866 --- /dev/null +++ b/bricktracker/sql/set/metadata/tag/insert.sql @@ -0,0 +1,14 @@ +BEGIN TRANSACTION; + +ALTER TABLE "bricktracker_set_tags" +ADD COLUMN "tag_{{ id }}" BOOLEAN NOT NULL DEFAULT 0; + +INSERT INTO "bricktracker_metadata_tags" ( + "id", + "name" +) VALUES ( + '{{ id }}', + '{{ name }}' +); + +COMMIT; \ No newline at end of file diff --git a/bricktracker/sql/set/metadata/tag/list.sql b/bricktracker/sql/set/metadata/tag/list.sql new file mode 100644 index 0000000..fe44b5f --- /dev/null +++ b/bricktracker/sql/set/metadata/tag/list.sql @@ -0,0 +1 @@ +{% extends 'set/metadata/tag/base.sql' %} diff --git a/bricktracker/sql/set/metadata/tag/select.sql b/bricktracker/sql/set/metadata/tag/select.sql new file mode 100644 index 0000000..2d52076 --- /dev/null +++ b/bricktracker/sql/set/metadata/tag/select.sql @@ -0,0 +1,5 @@ +{% extends 'set/metadata/tag/base.sql' %} + +{% block where %} +WHERE "bricktracker_metadata_tags"."id" IS NOT DISTINCT FROM :id +{% endblock %} \ No newline at end of file diff --git a/bricktracker/sql/set/metadata/tag/update/field.sql b/bricktracker/sql/set/metadata/tag/update/field.sql new file mode 100644 index 0000000..629a9e8 --- /dev/null +++ b/bricktracker/sql/set/metadata/tag/update/field.sql @@ -0,0 +1,3 @@ +UPDATE "bricktracker_metadata_tags" +SET "{{field}}" = :value +WHERE "bricktracker_metadata_tags"."id" IS NOT DISTINCT FROM :id diff --git a/bricktracker/sql/set/metadata/tag/update/state.sql b/bricktracker/sql/set/metadata/tag/update/state.sql new file mode 100644 index 0000000..18de40a --- /dev/null +++ b/bricktracker/sql/set/metadata/tag/update/state.sql @@ -0,0 +1,10 @@ +INSERT INTO "bricktracker_set_tags" ( + "id", + "{{name}}" +) VALUES ( + :set_id, + :state +) +ON CONFLICT("id") +DO UPDATE SET "{{name}}" = :state +WHERE "bricktracker_set_tags"."id" IS NOT DISTINCT FROM :set_id diff --git a/bricktracker/sql_counter.py b/bricktracker/sql_counter.py index 30c83d6..74c18cc 100644 --- a/bricktracker/sql_counter.py +++ b/bricktracker/sql_counter.py @@ -9,6 +9,7 @@ ALIASES: dict[str, Tuple[str, str]] = { 'bricktracker_set_owners': ('Bricktracker set owners', 'checkbox-line'), # noqa: E501 'bricktracker_set_statuses': ('Bricktracker set statuses', 'user-line'), # noqa: E501 'bricktracker_set_storages': ('Bricktracker set storages', 'archive-2-line'), # noqa: E501 + 'bricktracker_set_tags': ('Bricktracker set tags', 'price-tag-2-line'), # noqa: E501 'bricktracker_sets': ('Bricktracker sets', 'hashtag'), 'bricktracker_wishes': ('Bricktracker wishes', 'gift-line'), 'inventory': ('Parts', 'shapes-line'), diff --git a/bricktracker/version.py b/bricktracker/version.py index 996b4f6..767fad5 100644 --- a/bricktracker/version.py +++ b/bricktracker/version.py @@ -1,4 +1,4 @@ from typing import Final __version__: Final[str] = '1.2.0' -__database_version__: Final[int] = 13 +__database_version__: Final[int] = 14 diff --git a/bricktracker/views/add.py b/bricktracker/views/add.py index 20607dc..9072973 100644 --- a/bricktracker/views/add.py +++ b/bricktracker/views/add.py @@ -5,6 +5,8 @@ from ..configuration_list import BrickConfigurationList from .exceptions import exception_handler from ..set_owner import BrickSetOwner from ..set_owner_list import BrickSetOwnerList +from ..set_tag import BrickSetTag +from ..set_tag_list import BrickSetTagList from ..socket import MESSAGES add_page = Blueprint('add', __name__, url_prefix='/add') @@ -20,6 +22,7 @@ def add() -> str: return render_template( 'add.html', brickset_owners=BrickSetOwnerList(BrickSetOwner).list(), + brickset_tags=BrickSetTagList(BrickSetTag).list(), path=current_app.config['SOCKET_PATH'], namespace=current_app.config['SOCKET_NAMESPACE'], messages=MESSAGES @@ -36,6 +39,7 @@ def bulk() -> str: return render_template( 'add.html', brickset_owners=BrickSetOwnerList(BrickSetOwner).list(), + brickset_tags=BrickSetTagList(BrickSetTag).list(), path=current_app.config['SOCKET_PATH'], namespace=current_app.config['SOCKET_NAMESPACE'], messages=MESSAGES, diff --git a/bricktracker/views/admin/admin.py b/bricktracker/views/admin/admin.py index 36037d3..415cf48 100644 --- a/bricktracker/views/admin/admin.py +++ b/bricktracker/views/admin/admin.py @@ -12,6 +12,8 @@ from ...set_owner import BrickSetOwner from ...set_owner_list import BrickSetOwnerList from ...set_status import BrickSetStatus from ...set_status_list import BrickSetStatusList +from ...set_tag import BrickSetTag +from ...set_tag_list import BrickSetTagList from ...sql_counter import BrickCounter from ...sql import BrickSQL from ...theme_list import BrickThemeList @@ -32,6 +34,7 @@ def admin() -> str: database_version: int = -1 metadata_owners: list[BrickSetOwner] = [] metadata_statuses: list[BrickSetStatus] = [] + metadata_tags: list[BrickSetTag] = [] nil_minifigure_name: str = '' nil_minifigure_url: str = '' nil_part_name: str = '' @@ -46,6 +49,7 @@ def admin() -> str: metadata_owners = BrickSetOwnerList(BrickSetOwner).list() metadata_statuses = BrickSetStatusList(BrickSetStatus).list(all=True) + metadata_tags = BrickSetTagList(BrickSetTag).list() except Exception as e: database_exception = e @@ -72,6 +76,7 @@ def admin() -> str: open_owner = request.args.get('open_owner', None) open_retired = request.args.get('open_retired', None) open_status = request.args.get('open_status', None) + open_tag = request.args.get('open_tag', None) open_theme = request.args.get('open_theme', None) open_database = ( @@ -81,6 +86,7 @@ def admin() -> str: open_owner is None and open_retired is None and open_status is None and + open_tag is None and open_theme is None ) @@ -95,20 +101,23 @@ def admin() -> str: instructions=BrickInstructionsList(), metadata_owners=metadata_owners, metadata_statuses=metadata_statuses, + metadata_tags=metadata_tags, nil_minifigure_name=nil_minifigure_name, nil_minifigure_url=nil_minifigure_url, nil_part_name=nil_part_name, nil_part_url=nil_part_url, - open_status=open_status, open_database=open_database, open_image=open_image, open_instructions=open_instructions, open_logout=open_logout, open_owner=open_owner, open_retired=open_retired, + open_status=open_status, + open_tag=open_tag, open_theme=open_theme, owner_error=request.args.get('owner_error'), - status_error=request.args.get('status_error'), retired=BrickRetiredList(), + status_error=request.args.get('status_error'), + tag_error=request.args.get('tag_error'), theme=BrickThemeList(), ) diff --git a/bricktracker/views/admin/tag.py b/bricktracker/views/admin/tag.py new file mode 100644 index 0000000..d31bc49 --- /dev/null +++ b/bricktracker/views/admin/tag.py @@ -0,0 +1,84 @@ +from flask import ( + Blueprint, + redirect, + request, + render_template, + url_for, +) +from flask_login import login_required +from werkzeug.wrappers.response import Response + +from ..exceptions import exception_handler +from ...reload import reload +from ...set_tag import BrickSetTag + +admin_tag_page = Blueprint( + 'admin_tag', + __name__, + url_prefix='/admin/tag' +) + + +# Add a metadata tag +@admin_tag_page.route('/add', methods=['POST']) +@login_required +@exception_handler( + __file__, + post_redirect='admin.admin', + error_name='tag_error', + open_tag=True +) +def add() -> Response: + BrickSetTag().from_form(request.form).insert() + + reload() + + return redirect(url_for('admin.admin', open_tag=True)) + + +# Delete the metadata tag +@admin_tag_page.route('/delete', methods=['GET']) +@login_required +@exception_handler(__file__) +def delete(*, id: str) -> str: + return render_template( + 'admin.html', + delete_tag=True, + tag=BrickSetTag().select_specific(id), + error=request.args.get('tag_error') + ) + + +# Actually delete the metadata tag +@admin_tag_page.route('/delete', methods=['POST']) +@login_required +@exception_handler( + __file__, + post_redirect='admin_tag.delete', + error_name='tag_error' +) +def do_delete(*, id: str) -> Response: + tag = BrickSetTag().select_specific(id) + tag.delete() + + reload() + + return redirect(url_for('admin.admin', open_tag=True)) + + +# Rename the metadata tag +@admin_tag_page.route('/rename', methods=['POST']) +@login_required +@exception_handler( + __file__, + post_redirect='admin.admin', + error_name='tag_error', + open_tag=True +) +def rename(*, id: str) -> Response: + tag = BrickSetTag().select_specific(id) + tag.from_form(request.form).rename() + + reload() + + return redirect(url_for('admin.admin', open_tag=True)) diff --git a/bricktracker/views/index.py b/bricktracker/views/index.py index 3d8a55e..1cbcd56 100644 --- a/bricktracker/views/index.py +++ b/bricktracker/views/index.py @@ -6,6 +6,8 @@ from ..set_owner import BrickSetOwner from ..set_owner_list import BrickSetOwnerList from ..set_status import BrickSetStatus from ..set_status_list import BrickSetStatusList +from ..set_tag import BrickSetTag +from ..set_tag_list import BrickSetTagList from ..set_list import BrickSetList index_page = Blueprint('index', __name__) @@ -20,5 +22,6 @@ def index() -> str: brickset_collection=BrickSetList().last(), brickset_owners=BrickSetOwnerList(BrickSetOwner).list(), brickset_statuses=BrickSetStatusList(BrickSetStatus).list(), + brickset_tags=BrickSetTagList(BrickSetTag).list(), minifigure_collection=BrickMinifigureList().last(), ) diff --git a/bricktracker/views/set.py b/bricktracker/views/set.py index fd922ec..344b0e6 100644 --- a/bricktracker/views/set.py +++ b/bricktracker/views/set.py @@ -16,11 +16,13 @@ from .exceptions import exception_handler from ..minifigure import BrickMinifigure from ..part import BrickPart from ..set import BrickSet +from ..set_list import BrickSetList from ..set_owner import BrickSetOwner from ..set_owner_list import BrickSetOwnerList from ..set_status import BrickSetStatus from ..set_status_list import BrickSetStatusList -from ..set_list import BrickSetList +from ..set_tag import BrickSetTag +from ..set_tag_list import BrickSetTagList from ..socket import MESSAGES logger = logging.getLogger(__name__) @@ -37,6 +39,7 @@ def list() -> str: collection=BrickSetList().all(), brickset_owners=BrickSetOwnerList(BrickSetOwner).list(), brickset_statuses=BrickSetStatusList(BrickSetStatus).list(), + brickset_tags=BrickSetTagList(BrickSetTag).list(), ) @@ -66,6 +69,19 @@ def update_status(*, id: str, metadata_id: str) -> Response: return jsonify({'value': state}) +# Change the state of a tag +@set_page.route('//tag/', methods=['POST']) +@login_required +@exception_handler(__file__, json=True) +def update_tag(*, id: str, metadata_id: str) -> Response: + brickset = BrickSet().select_light(id) + tag = BrickSetTagList(BrickSetTag).get(metadata_id) + + state = tag.update_set_state(brickset, json=request.json) + + return jsonify({'value': state}) + + # Ask for deletion of a set @set_page.route('//delete', methods=['GET']) @login_required @@ -116,6 +132,7 @@ def details(*, id: str) -> str: open_instructions=request.args.get('open_instructions'), brickset_owners=BrickSetOwnerList(BrickSetOwner).list(), brickset_statuses=BrickSetStatusList(BrickSetStatus).list(all=True), + brickset_tags=BrickSetTagList(BrickSetTag).list(), ) diff --git a/static/scripts/socket/set.js b/static/scripts/socket/set.js index 08e70dd..a7e660b 100644 --- a/static/scripts/socket/set.js +++ b/static/scripts/socket/set.js @@ -16,6 +16,7 @@ class BrickSetSocket extends BrickSocket { this.html_input = document.getElementById(`${id}-set`); this.html_no_confim = document.getElementById(`${id}-no-confirm`); this.html_owners = document.getElementById(`${id}-owners`); + this.html_tags = document.getElementById(`${id}-tags`); // Card elements this.html_card = document.getElementById(`${id}-card`); @@ -150,11 +151,22 @@ class BrickSetSocket extends BrickSocket { }); } + // Grab the tags + const tags = []; + if (this.html_tags) { + this.html_tags.querySelectorAll('input').forEach(input => { + if (input.checked) { + tags.push(input.value); + } + }); + } + this.spinner(true); this.socket.emit(this.messages.IMPORT_SET, { set: (set !== undefined) ? set : this.html_input.value, owners: owners, + tags: tags, refresh: this.refresh }); } else { @@ -267,6 +279,10 @@ class BrickSetSocket extends BrickSocket { this.html_owners.querySelectorAll('input').forEach(input => input.disabled = !enabled); } + if (this.html_tags) { + this.html_tags.querySelectorAll('input').forEach(input => input.disabled = !enabled); + } + if (this.html_card_confirm) { this.html_card_confirm.disabled = !enabled; } diff --git a/templates/add.html b/templates/add.html index 59c5029..3a0b784 100644 --- a/templates/add.html +++ b/templates/add.html @@ -46,6 +46,19 @@ {% endfor %} {% endif %} + {% if brickset_tags | length %} +
Tags
+
+ {% for tag in brickset_tags %} + {% with id=tag.as_dataset() %} +
+ + +
+ {% endwith %} + {% endfor %} +
+ {% endif %}

diff --git a/templates/admin.html b/templates/admin.html index 962730b..064526d 100644 --- a/templates/admin.html +++ b/templates/admin.html @@ -18,6 +18,8 @@ {% include 'admin/owner/delete.html' %} {% elif delete_status %} {% include 'admin/status/delete.html' %} + {% elif delete_tag %} + {% include 'admin/tag/delete.html' %} {% elif drop_database %} {% include 'admin/database/drop.html' %} {% elif import_database %} @@ -34,6 +36,7 @@ {% include 'admin/retired.html' %} {% include 'admin/owner.html' %} {% include 'admin/status.html' %} + {% include 'admin/tag.html' %} {% include 'admin/database.html' %} {% include 'admin/configuration.html' %} {% endif %} diff --git a/templates/admin/tag.html b/templates/admin/tag.html new file mode 100644 index 0000000..7c67a56 --- /dev/null +++ b/templates/admin/tag.html @@ -0,0 +1,42 @@ +{% import 'macro/accordion.html' as accordion %} + +{{ accordion.header('Set tags', 'tag', 'admin', expanded=open_tag, icon='price-tag-2-line', class='p-0') }} +{% if tag_error %}

{% endif %} +
    + {% if metadata_tags | length %} + {% for tag in metadata_tags %} +
  • +
    +
    + +
    +
    Name
    + + +
    +
    +
    + Delete +
    +
    +
  • + {% endfor %} + {% else %} +
  • No tag found.
  • + {% endif %} +
  • +
    +
    + +
    +
    Name
    + +
    +
    +
    + +
    +
    +
  • +
+{{ accordion.footer() }} diff --git a/templates/admin/tag/delete.html b/templates/admin/tag/delete.html new file mode 100644 index 0000000..69dd334 --- /dev/null +++ b/templates/admin/tag/delete.html @@ -0,0 +1,19 @@ +{% import 'macro/accordion.html' as accordion %} + +{{ accordion.header('Set tags danger zone', 'tag-danger', 'admin', expanded=true, danger=true, class='text-end') }} +
+ {% if tag_error %}{% endif %} + +
+
+
+
Name
+ +
+
+
+
+ Back to the admin + +
+{{ accordion.footer() }} diff --git a/templates/macro/badge.html b/templates/macro/badge.html index 2be95ca..ca57769 100644 --- a/templates/macro/badge.html +++ b/templates/macro/badge.html @@ -51,7 +51,7 @@ {% endmacro %} {% macro owner(item, owner, solo=false, last=false) %} - {% if last %} + {% if last %} {% set tooltip=owner.fields.name %} {% else %} {% set text=owner.fields.name %} @@ -72,8 +72,17 @@ {{ badge(check=set, url=url, solo=solo, last=last, color='secondary', icon='hashtag', collapsible='Set:', text=set, alt='Set') }} {% endmacro %} +{% macro tag(item, tag, solo=false, last=false) %} + {% if last %} + {% set tooltip=tag.fields.name %} + {% else %} + {% set text=tag.fields.name %} + {% endif %} + {{ badge(check=item.fields[tag.as_column()], solo=solo, last=last, color='light text-primary-emphasis bg-primary-subtle border border-primary-subtle', icon='price-tag-2-line', text=text, alt='Tag', tooltip=tooltip) }} +{% endmacro %} + {% macro theme(theme, solo=false, last=false) %} - {% if last %} + {% if last %} {% set tooltip=theme %} {% else %} {% set text=theme %} diff --git a/templates/set/card.html b/templates/set/card.html index 9f2f83a..bc302e3 100644 --- a/templates/set/card.html +++ b/templates/set/card.html @@ -12,7 +12,13 @@ {% for owner in brickset_owners %} {% with checked=item.fields[owner.as_column()] %} data-{{ owner.as_dataset() }}="{{ checked }}" - {% if checked %} data-owner-{{ loop.index }}="{{ owner.fields.name | lower }}"{% endif %} + {% if checked %} data-search-owner-{{ loop.index }}="{{ owner.fields.name | lower }}"{% endif %} + {% endwith %} + {% endfor %} + {% for tag in brickset_tags %} + {% with checked=item.fields[tag.as_column()] %} + data-{{ tag.as_dataset() }}="{{ checked }}" + {% if checked %} data-search-tag-{{ loop.index }}="{{ tag.fields.name | lower }}"{% endif %} {% endwith %} {% endfor %} {% endif %} @@ -21,6 +27,9 @@ {{ card.image(item, solo=solo, last=last, caption=item.fields.name, alt=item.fields.set) }}
{{ badge.theme(item.theme.name, solo=solo, last=last) }} + {% for tag in brickset_tags %} + {{ badge.tag(item, tag, solo=solo, last=last) }} + {% endfor %} {{ badge.year(item.fields.year, solo=solo, last=last) }} {{ badge.parts(item.fields.number_of_parts, solo=solo, last=last) }} {{ badge.total_minifigures(item.fields.total_minifigures, solo=solo, last=last) }} diff --git a/templates/set/management.html b/templates/set/management.html index 957db81..b1b2019 100644 --- a/templates/set/management.html +++ b/templates/set/management.html @@ -1,20 +1,34 @@ {% if g.login.is_authenticated() %} -{{ accordion.header('Owners', 'owner', 'set-details', icon='group-line', class='p-0') }} -
    - {% if brickset_owners | length %} - {% for owner in brickset_owners %} -
  • {{ form.checkbox(item, owner, delete=delete) }}
  • - {% endfor %} - {% else %} -
  • No owner found.
  • - {% endif %} -
- -{{ accordion.footer() }} -{{ accordion.header('Management', 'management', 'set-details', expanded=true, icon='settings-4-line') }} -
Data
- Refresh the set data -{{ accordion.footer() }} + {{ accordion.header('Owners', 'owner', 'set-details', icon='group-line', class='p-0') }} +
    + {% if brickset_owners | length %} + {% for owner in brickset_owners %} +
  • {{ form.checkbox(item, owner, delete=delete) }}
  • + {% endfor %} + {% else %} +
  • No owner found.
  • + {% endif %} +
+ + {{ accordion.footer() }} + {{ accordion.header('Tags', 'tag', 'set-details', icon='price-tag-2-line', class='p-0') }} +
    + {% if brickset_tags | length %} + {% for tag in brickset_tags %} +
  • {{ form.checkbox(item, tag, delete=delete) }}
  • + {% endfor %} + {% else %} +
  • No tag found.
  • + {% endif %} +
+ + {{ accordion.footer() }} + {{ accordion.header('Management', 'management', 'set-details', expanded=true, icon='settings-4-line') }} +
Data
+ Refresh the set data + {{ accordion.footer() }} {% endif %} diff --git a/templates/sets.html b/templates/sets.html index b06c94d..7541f1c 100644 --- a/templates/sets.html +++ b/templates/sets.html @@ -10,7 +10,7 @@
Search - +
@@ -89,6 +89,20 @@
+
+ +
+ Tag + +
+
{% for item in collection %}