This commit is contained in:
2025-01-31 18:08:53 +01:00
parent 5ad94078ed
commit f34bbe0602
36 changed files with 424 additions and 30 deletions
+2
View File
@@ -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
+2 -1
View File
@@ -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
+2
View File
@@ -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',
+5
View File
@@ -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)
+11 -1
View File
@@ -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(
+6 -2
View File
@@ -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)
+16
View File
@@ -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'
+17
View File
@@ -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'
+19
View File
@@ -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;
+2
View File
@@ -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";
+3
View File
@@ -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 %}
+5
View File
@@ -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
+3
View File
@@ -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 }}';
@@ -0,0 +1,6 @@
SELECT
"bricktracker_metadata_tags"."id",
"bricktracker_metadata_tags"."name"
FROM "bricktracker_metadata_tags"
{% block where %}{% endblock %}
@@ -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;
@@ -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;
@@ -0,0 +1 @@
{% extends 'set/metadata/tag/base.sql' %}
@@ -0,0 +1,5 @@
{% extends 'set/metadata/tag/base.sql' %}
{% block where %}
WHERE "bricktracker_metadata_tags"."id" IS NOT DISTINCT FROM :id
{% endblock %}
@@ -0,0 +1,3 @@
UPDATE "bricktracker_metadata_tags"
SET "{{field}}" = :value
WHERE "bricktracker_metadata_tags"."id" IS NOT DISTINCT FROM :id
@@ -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
+1
View File
@@ -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'),
+1 -1
View File
@@ -1,4 +1,4 @@
from typing import Final
__version__: Final[str] = '1.2.0'
__database_version__: Final[int] = 13
__database_version__: Final[int] = 14
+4
View File
@@ -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,
+11 -2
View File
@@ -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(),
)
+84
View File
@@ -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('<id>/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('<id>/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('<id>/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))
+3
View File
@@ -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(),
)
+18 -1
View File
@@ -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('/<id>/tag/<metadata_id>', 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('/<id>/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(),
)