From 3bbcf2c21c638560845c3a29521a63d8ac4b1162 Mon Sep 17 00:00:00 2001 From: Gregoo Date: Sun, 26 Jan 2025 09:59:53 +0100 Subject: [PATCH 01/19] Add a flag to hide instructions in a set card --- .env.sample | 4 ++++ bricktracker/config.py | 1 + bricktracker/set.py | 7 +++++-- templates/set/card.html | 2 +- 4 files changed, 11 insertions(+), 3 deletions(-) diff --git a/.env.sample b/.env.sample index 7cbc5c0..06584db 100644 --- a/.env.sample +++ b/.env.sample @@ -111,6 +111,10 @@ # Default: false # BK_HIDE_MISSING_PARTS=true +# Optional: Hide the 'Instructions' entry in a Set card +# Default: false +# BK_HIDE_SET_INSTRUCTIONS=true + # Optional: Hide the 'Wishlist' entry from the menu. Does not disable the route. # Default: false # BK_HIDE_WISHES=true diff --git a/bricktracker/config.py b/bricktracker/config.py index 08db61b..f9d8f2b 100644 --- a/bricktracker/config.py +++ b/bricktracker/config.py @@ -30,6 +30,7 @@ CONFIG: Final[list[dict[str, Any]]] = [ {'n': 'HIDE_ALL_PARTS', 'c': bool}, {'n': 'HIDE_ALL_SETS', 'c': bool}, {'n': 'HIDE_MISSING_PARTS', 'c': bool}, + {'n': 'HIDE_SET_INSTRUCTIONS', 'c': bool}, {'n': 'HIDE_WISHES', 'c': bool}, {'n': 'MINIFIGURES_DEFAULT_ORDER', 'd': '"minifigures"."name" ASC'}, {'n': 'MINIFIGURES_FOLDER', 'd': 'minifigs', 's': True}, diff --git a/bricktracker/set.py b/bricktracker/set.py index aa536b8..f16ea6f 100644 --- a/bricktracker/set.py +++ b/bricktracker/set.py @@ -3,7 +3,7 @@ import traceback from typing import Any, Self from uuid import uuid4 -from flask import url_for +from flask import current_app, url_for from .exceptions import DatabaseException, NotFoundException from .minifigure_list import BrickMinifigureList @@ -179,7 +179,10 @@ class BrickSet(RebrickableSet): # Compute the url for the set instructions def url_for_instructions(self, /) -> str: - if len(self.instructions): + if ( + not current_app.config['HIDE_SET_INSTRUCTIONS'] and + len(self.instructions) + ): return url_for( 'set.details', id=self.fields.id, diff --git a/templates/set/card.html b/templates/set/card.html index 9fcea3d..1308c71 100644 --- a/templates/set/card.html +++ b/templates/set/card.html @@ -35,7 +35,7 @@ {% endfor %} {% endif %} - {% if solo %} + {% if solo and not config['HIDE_SET_INSTRUCTIONS'] %}
{% if not delete %} {{ accordion.header('Instructions', 'instructions', 'set-details', expanded=open_instructions, quantity=item.instructions | length, icon='file-line', class='p-0') }} -- 2.45.2 From fa44b31f5138c3622179a7381decbbb678ab524f Mon Sep 17 00:00:00 2001 From: Gregoo Date: Sun, 26 Jan 2025 10:29:33 +0100 Subject: [PATCH 02/19] Grey out legacy database tables in the admin --- bricktracker/sql_counter.py | 3 +++ templates/admin/database.html | 4 ++-- 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/bricktracker/sql_counter.py b/bricktracker/sql_counter.py index d104269..7175494 100644 --- a/bricktracker/sql_counter.py +++ b/bricktracker/sql_counter.py @@ -22,6 +22,7 @@ class BrickCounter(object): table: str icon: str count: int + legacy: bool def __init__( self, @@ -44,3 +45,5 @@ class BrickCounter(object): self.name = name self.icon = icon + + self.legacy = '(legacy)' in self.name diff --git a/templates/admin/database.html b/templates/admin/database.html index 36d2b0d..9704365 100644 --- a/templates/admin/database.html +++ b/templates/admin/database.html @@ -22,8 +22,8 @@
    {% for counter in database_counters %} -
  • - {{ counter.name }} {{ counter.count }} +
  • + {{ counter.name }} {{ counter.count }}
  • {% if not (loop.index % 5) %}
-- 2.45.2 From 35208fec8a7a44b99cc6a64179b7a14bdcb41b0a Mon Sep 17 00:00:00 2001 From: Gregoo Date: Mon, 27 Jan 2025 10:04:24 +0100 Subject: [PATCH 03/19] Rename download_rebrickable to insert_rebrickable and make it return if an insertion occured --- bricktracker/rebrickable_set.py | 10 +++++++--- bricktracker/set.py | 2 +- 2 files changed, 8 insertions(+), 4 deletions(-) diff --git a/bricktracker/rebrickable_set.py b/bricktracker/rebrickable_set.py index 5a1c41f..23072d3 100644 --- a/bricktracker/rebrickable_set.py +++ b/bricktracker/rebrickable_set.py @@ -52,8 +52,8 @@ class RebrickableSet(BrickRecord): if record is not None: self.ingest(record) - # Import the set from Rebrickable - def download_rebrickable(self, /) -> None: + # Insert the set from Rebrickable + def insert_rebrickable(self, /) -> bool: # Insert the Rebrickable set to the database rows, _ = self.insert( commit=False, @@ -61,10 +61,14 @@ class RebrickableSet(BrickRecord): override_query=RebrickableSet.insert_query ) - if rows > 0: + inserted = rows > 0 + + if inserted: if not current_app.config['USE_REMOTE_IMAGES']: RebrickableImage(self).download() + return inserted + # Ingest a set def ingest(self, record: Row | dict[str, Any], /): super().ingest(record) diff --git a/bricktracker/set.py b/bricktracker/set.py index f16ea6f..cdd5678 100644 --- a/bricktracker/set.py +++ b/bricktracker/set.py @@ -54,7 +54,7 @@ class BrickSet(RebrickableSet): self.insert(commit=False) # Execute the parent download method - self.download_rebrickable() + self.insert_rebrickable() # Load the inventory RebrickableParts(self.socket, self).download() -- 2.45.2 From 5f847168f4ef793b9cc2db968b22c40d4a7dd640 Mon Sep 17 00:00:00 2001 From: Gregoo Date: Mon, 27 Jan 2025 10:04:37 +0100 Subject: [PATCH 04/19] Remove unused insert_rebrickable --- bricktracker/set.py | 4 ---- 1 file changed, 4 deletions(-) diff --git a/bricktracker/set.py b/bricktracker/set.py index cdd5678..3e348e9 100644 --- a/bricktracker/set.py +++ b/bricktracker/set.py @@ -97,10 +97,6 @@ class BrickSet(RebrickableSet): logger.debug(traceback.format_exc()) - # Insert a Rebrickable set - def insert_rebrickable(self, /) -> None: - self.insert() - # Minifigures def minifigures(self, /) -> BrickMinifigureList: return BrickMinifigureList().load(self) -- 2.45.2 From 9c925b56dad57c3d726f90afecc4c99d43aad01a Mon Sep 17 00:00:00 2001 From: Gregoo Date: Mon, 27 Jan 2025 11:21:15 +0100 Subject: [PATCH 05/19] Rename routes --- bricktracker/views/minifigure.py | 10 ++++----- bricktracker/views/set.py | 35 ++++++++++++++------------------ 2 files changed, 20 insertions(+), 25 deletions(-) diff --git a/bricktracker/views/minifigure.py b/bricktracker/views/minifigure.py index 29c5822..60647fa 100644 --- a/bricktracker/views/minifigure.py +++ b/bricktracker/views/minifigure.py @@ -19,12 +19,12 @@ def list() -> str: # Minifigure details -@minifigure_page.route('//details') +@minifigure_page.route('/
/details') @exception_handler(__file__) -def details(*, number: str) -> str: +def details(*, figure: str) -> str: return render_template( 'minifigure.html', - item=BrickMinifigure().select_generic(number), - using=BrickSetList().using_minifigure(number), - missing=BrickSetList().missing_minifigure(number), + item=BrickMinifigure().select_generic(figure), + using=BrickSetList().using_minifigure(figure), + missing=BrickSetList().missing_minifigure(figure), ) diff --git a/bricktracker/views/set.py b/bricktracker/views/set.py index b36f7e1..02353c1 100644 --- a/bricktracker/views/set.py +++ b/bricktracker/views/set.py @@ -108,33 +108,28 @@ def details(*, id: str) -> str: # Update the missing pieces of a minifig part -@set_page.route('//minifigures//parts//missing', methods=['POST']) # noqa: E501 +@set_page.route('//minifigures/
/parts//missing', methods=['POST']) # noqa: E501 @login_required @exception_handler(__file__, json=True) -def missing_minifigure_part( - *, - id: str, - minifigure_id: str, - part_id: str -) -> Response: +def missing_minifigure_part(*, id: str, figure: str, part: str) -> Response: brickset = BrickSet().select_specific(id) - minifigure = BrickMinifigure().select_specific(brickset, minifigure_id) - part = BrickPart().select_specific( + brickminifigure = BrickMinifigure().select_specific(brickset, figure) + brickpart = BrickPart().select_specific( brickset, - part_id, - minifigure=minifigure, + part, + minifigure=brickminifigure, ) missing = request.json.get('missing', '') # type: ignore - part.update_missing(missing) + brickpart.update_missing(missing) # Info - logger.info('Set {number} ({id}): updated minifigure ({minifigure}) part ({part}) missing count to {missing}'.format( # noqa: E501 + logger.info('Set {number} ({id}): updated minifigure ({figure}) part ({part}) missing count to {missing}'.format( # noqa: E501 number=brickset.fields.set, id=brickset.fields.id, - minifigure=minifigure.fields.fig_num, - part=part.fields.id, + figure=brickminifigure.fields.fig_num, + part=brickpart.fields.id, missing=missing, )) @@ -142,22 +137,22 @@ def missing_minifigure_part( # Update the missing pieces of a part -@set_page.route('//parts//missing', methods=['POST']) +@set_page.route('//parts//missing', methods=['POST']) @login_required @exception_handler(__file__, json=True) -def missing_part(*, id: str, part_id: str) -> Response: +def missing_part(*, id: str, part: str) -> Response: brickset = BrickSet().select_specific(id) - part = BrickPart().select_specific(brickset, part_id) + brickpart = BrickPart().select_specific(brickset, part) missing = request.json.get('missing', '') # type: ignore - part.update_missing(missing) + brickpart.update_missing(missing) # Info logger.info('Set {number} ({id}): updated part ({part}) missing count to {missing}'.format( # noqa: E501 number=brickset.fields.set, id=brickset.fields.id, - part=part.fields.id, + part=brickpart.fields.id, missing=missing, )) -- 2.45.2 From 579ec886290ea99f2b793a488c9cf23b454a6216 Mon Sep 17 00:00:00 2001 From: Gregoo Date: Mon, 27 Jan 2025 12:04:20 +0100 Subject: [PATCH 06/19] Remove confusing reference to number for sets --- bricktracker/set.py | 24 ++++++++++++------------ bricktracker/wish.py | 4 ++-- 2 files changed, 14 insertions(+), 14 deletions(-) diff --git a/bricktracker/set.py b/bricktracker/set.py index 3e348e9..523d336 100644 --- a/bricktracker/set.py +++ b/bricktracker/set.py @@ -41,8 +41,8 @@ class BrickSet(RebrickableSet): try: # Insert into the database self.socket.auto_progress( - message='Set {number}: inserting into database'.format( - number=self.fields.set + message='Set {set}: inserting into database'.format( + set=self.fields.set ), increment_total=True, ) @@ -64,8 +64,8 @@ class BrickSet(RebrickableSet): # Commit the transaction to the database self.socket.auto_progress( - message='Set {number}: writing to the database'.format( - number=self.fields.set + message='Set {set}: writing to the database'.format( + set=self.fields.set ), increment_total=True, ) @@ -73,15 +73,15 @@ class BrickSet(RebrickableSet): BrickSQL().commit() # Info - logger.info('Set {number}: imported (id: {id})'.format( - number=self.fields.set, + logger.info('Set {set}: imported (id: {id})'.format( + set=self.fields.set, id=self.fields.id, )) # Complete self.socket.complete( - message='Set {number}: imported (Go to the set)'.format( # noqa: E501 - number=self.fields.set, + message='Set {set}: imported (Go to the set)'.format( # noqa: E501 + set=self.fields.set, url=self.url() ), download=True @@ -89,8 +89,8 @@ class BrickSet(RebrickableSet): except Exception as e: self.socket.fail( - message='Error while importing set {number}: {error}'.format( - number=self.fields.set, + message='Error while importing set {set}: {error}'.format( + set=self.fields.set, error=e, ) ) @@ -155,9 +155,9 @@ class BrickSet(RebrickableSet): ) if rows != 1: - raise DatabaseException('Could not update the status "{status}" for set {number} ({id})'.format( # noqa: E501 + raise DatabaseException('Could not update the status "{status}" for set {set} ({id})'.format( # noqa: E501 status=checkbox.fields.name, - number=self.fields.set, + set=self.fields.set, id=self.fields.id, )) diff --git a/bricktracker/wish.py b/bricktracker/wish.py index 1e301fa..def41e2 100644 --- a/bricktracker/wish.py +++ b/bricktracker/wish.py @@ -31,8 +31,8 @@ class BrickWish(RebrickableSet): # Load from database if not self.select(): raise NotFoundException( - 'Wish with number {number} was not found in the database'.format( # noqa: E501 - number=self.fields.set, + 'Wish for set {set} was not found in the database'.format( # noqa: E501 + set=self.fields.set, ), ) -- 2.45.2 From f8ba0abbbe999d9ae7e254c28cef7925575000e5 Mon Sep 17 00:00:00 2001 From: Gregoo Date: Mon, 27 Jan 2025 14:15:07 +0100 Subject: [PATCH 07/19] Provide decorator for socket actions, for repetitive tasks like checking if authenticated or ready for Rebrickable actions --- bricktracker/socket.py | 74 +++---------------------- bricktracker/socket_decorator.py | 93 ++++++++++++++++++++++++++++++++ 2 files changed, 101 insertions(+), 66 deletions(-) create mode 100644 bricktracker/socket_decorator.py diff --git a/bricktracker/socket.py b/bricktracker/socket.py index c7215ae..3fbd6cd 100644 --- a/bricktracker/socket.py +++ b/bricktracker/socket.py @@ -1,14 +1,13 @@ import logging from typing import Any, Final, Tuple -from flask import copy_current_request_context, Flask, request +from flask import Flask, request from flask_socketio import SocketIO -from .configuration_list import BrickConfigurationList from .instructions import BrickInstructions from .instructions_list import BrickInstructionsList -from .login import LoginManager from .set import BrickSet +from .socket_decorator import authenticated_socket, rebrickable_socket from .sql import close as sql_close logger = logging.getLogger(__name__) @@ -87,12 +86,8 @@ class BrickSocket(object): self.disconnected() @self.socket.on(MESSAGES['DOWNLOAD_INSTRUCTIONS'], namespace=self.namespace) # noqa: E501 + @authenticated_socket(self) def download_instructions(data: dict[str, Any], /) -> None: - # Needs to be authenticated - if LoginManager.is_not_authenticated(): - self.fail(message='You need to be authenticated') - return - instructions = BrickInstructions( '{name}.pdf'.format(name=data.get('alt', '')), socket=self @@ -107,71 +102,18 @@ class BrickSocket(object): except Exception: pass - # Start it in a thread if requested - if self.threaded: - @copy_current_request_context - def do_download() -> None: - instructions.download(path) + instructions.download(path) - BrickInstructionsList(force=True) - - self.socket.start_background_task(do_download) - else: - instructions.download(path) - - BrickInstructionsList(force=True) + BrickInstructionsList(force=True) @self.socket.on(MESSAGES['IMPORT_SET'], namespace=self.namespace) + @rebrickable_socket(self) def import_set(data: dict[str, Any], /) -> None: - # Needs to be authenticated - if LoginManager.is_not_authenticated(): - self.fail(message='You need to be authenticated') - return - - # Needs the Rebrickable API key - try: - BrickConfigurationList.error_unless_is_set('REBRICKABLE_API_KEY') # noqa: E501 - except Exception as e: - self.fail(message=str(e)) - return - - brickset = BrickSet(socket=self) - - # Start it in a thread if requested - if self.threaded: - @copy_current_request_context - def do_download() -> None: - brickset.download(data) - - self.socket.start_background_task(do_download) - else: - brickset.download(data) + BrickSet(socket=self).download(data) @self.socket.on(MESSAGES['LOAD_SET'], namespace=self.namespace) def load_set(data: dict[str, Any], /) -> None: - # Needs to be authenticated - if LoginManager.is_not_authenticated(): - self.fail(message='You need to be authenticated') - return - - # Needs the Rebrickable API key - try: - BrickConfigurationList.error_unless_is_set('REBRICKABLE_API_KEY') # noqa: E501 - except Exception as e: - self.fail(message=str(e)) - return - - brickset = BrickSet(socket=self) - - # Start it in a thread if requested - if self.threaded: - @copy_current_request_context - def do_load() -> None: - brickset.load(data) - - self.socket.start_background_task(do_load) - else: - brickset.load(data) + BrickSet(socket=self).load(data) # Update the progress auto-incrementing def auto_progress( diff --git a/bricktracker/socket_decorator.py b/bricktracker/socket_decorator.py new file mode 100644 index 0000000..331b457 --- /dev/null +++ b/bricktracker/socket_decorator.py @@ -0,0 +1,93 @@ +from functools import wraps +from threading import Thread +from typing import Callable, ParamSpec, TYPE_CHECKING, Union + +from flask import copy_current_request_context + +from .configuration_list import BrickConfigurationList +from .login import LoginManager +if TYPE_CHECKING: + from .socket import BrickSocket + +# What a threaded function can return (None or Thread) +SocketReturn = Union[None, Thread] + +# Threaded signature (*arg, **kwargs -> (None or Thread) +P = ParamSpec('P') +SocketCallable = Callable[P, SocketReturn] + + +# Fail if not authenticated +def authenticated_socket( + self: 'BrickSocket', + /, + *, + threaded: bool = True, +) -> Callable[[SocketCallable], SocketCallable]: + def outer(function: SocketCallable, /) -> SocketCallable: + @wraps(function) + def wrapper(*args, **kwargs) -> SocketReturn: + # Needs to be authenticated + if LoginManager.is_not_authenticated(): + self.fail(message='You need to be authenticated') + return + + # Apply threading + if threaded: + return threaded_socket(self)(function)(*args, **kwargs) + else: + return function(*args, **kwargs) + + return wrapper + return outer + + +# Fail if not ready for Rebrickable (authenticated, API key) +# Automatically makes it threaded +def rebrickable_socket( + self: 'BrickSocket', + /, + *, + threaded: bool = True, +) -> Callable[[SocketCallable], SocketCallable]: + def outer(function: SocketCallable, /) -> SocketCallable: + @wraps(function) + # Automatically authenticated + @authenticated_socket(self, threaded=False) + def wrapper(*args, **kwargs) -> SocketReturn: + # Needs the Rebrickable API key + try: + BrickConfigurationList.error_unless_is_set('REBRICKABLE_API_KEY') # noqa: E501 + except Exception as e: + self.fail(message=str(e)) + return + + # Apply threading + if threaded: + return threaded_socket(self)(function)(*args, **kwargs) + else: + return function(*args, **kwargs) + + return wrapper + return outer + + +# Start the function in a thread if the socket is threaded +def threaded_socket( + self: 'BrickSocket', + / +) -> Callable[[SocketCallable], SocketCallable]: + def outer(function: SocketCallable, /) -> SocketCallable: + @wraps(function) + def wrapper(*args, **kwargs) -> SocketReturn: + # Start it in a thread if requested + if self.threaded: + @copy_current_request_context + def do_function() -> None: + function(*args, **kwargs) + + return self.socket.start_background_task(do_function) + else: + return function(*args, **kwargs) + return wrapper + return outer -- 2.45.2 From 29bb92ef9386ff2d334521966a3ffab197ab6ad2 Mon Sep 17 00:00:00 2001 From: Gregoo Date: Mon, 27 Jan 2025 14:20:12 +0100 Subject: [PATCH 08/19] Inject the socket only where necessary --- bricktracker/rebrickable_set.py | 21 ++++++++------------- bricktracker/set.py | 20 +++++++++++--------- bricktracker/socket.py | 4 ++-- 3 files changed, 21 insertions(+), 24 deletions(-) diff --git a/bricktracker/rebrickable_set.py b/bricktracker/rebrickable_set.py index 23072d3..1cd4b8d 100644 --- a/bricktracker/rebrickable_set.py +++ b/bricktracker/rebrickable_set.py @@ -21,7 +21,6 @@ logger = logging.getLogger(__name__) # A set from Rebrickable class RebrickableSet(BrickRecord): - socket: 'BrickSocket' theme: 'BrickTheme' instructions: list[BrickInstructions] @@ -36,7 +35,6 @@ class RebrickableSet(BrickRecord): self, /, *, - socket: 'BrickSocket | None' = None, record: Row | dict[str, Any] | None = None ): super().__init__() @@ -44,10 +42,6 @@ class RebrickableSet(BrickRecord): # Placeholders self.instructions = [] - # Save the socket - if socket is not None: - self.socket = socket - # Ingest the record if it has one if record is not None: self.ingest(record) @@ -92,20 +86,21 @@ class RebrickableSet(BrickRecord): # Load the set from Rebrickable def load( self, + socket: 'BrickSocket', data: dict[str, Any], /, *, from_download=False, ) -> bool: # Reset the progress - self.socket.progress_count = 0 - self.socket.progress_total = 2 + socket.progress_count = 0 + socket.progress_total = 2 try: - self.socket.auto_progress(message='Parsing set number') + socket.auto_progress(message='Parsing set number') set = parse_set(str(data['set'])) - self.socket.auto_progress( + socket.auto_progress( message='Set {set}: loading from Rebrickable'.format( set=set, ), @@ -122,12 +117,12 @@ class RebrickableSet(BrickRecord): instance=self, ).get() - self.socket.emit('SET_LOADED', self.short( + socket.emit('SET_LOADED', self.short( from_download=from_download )) if not from_download: - self.socket.complete( + socket.complete( message='Set {set}: loaded from Rebrickable'.format( set=self.fields.set ) @@ -136,7 +131,7 @@ class RebrickableSet(BrickRecord): return True except Exception as e: - self.socket.fail( + socket.fail( message='Could not load the set from Rebrickable: {error}. Data: {data}'.format( # noqa: E501 error=str(e), data=data, diff --git a/bricktracker/set.py b/bricktracker/set.py index 523d336..e521268 100644 --- a/bricktracker/set.py +++ b/bricktracker/set.py @@ -1,6 +1,6 @@ import logging import traceback -from typing import Any, Self +from typing import Any, Self, TYPE_CHECKING from uuid import uuid4 from flask import current_app, url_for @@ -14,6 +14,8 @@ from .rebrickable_set import RebrickableSet from .set_checkbox import BrickSetCheckbox from .set_checkbox_list import BrickSetCheckboxList from .sql import BrickSQL +if TYPE_CHECKING: + from .socket import BrickSocket logger = logging.getLogger(__name__) @@ -33,14 +35,14 @@ class BrickSet(RebrickableSet): ) # Import a set into the database - def download(self, data: dict[str, Any], /) -> None: + def download(self, socket: 'BrickSocket', data: dict[str, Any], /) -> None: # Load the set - if not self.load(data, from_download=True): + if not self.load(socket, data, from_download=True): return try: # Insert into the database - self.socket.auto_progress( + socket.auto_progress( message='Set {set}: inserting into database'.format( set=self.fields.set ), @@ -57,13 +59,13 @@ class BrickSet(RebrickableSet): self.insert_rebrickable() # Load the inventory - RebrickableParts(self.socket, self).download() + RebrickableParts(socket, self).download() # Load the minifigures - RebrickableMinifigures(self.socket, self).download() + RebrickableMinifigureList(socket, self).download() # Commit the transaction to the database - self.socket.auto_progress( + socket.auto_progress( message='Set {set}: writing to the database'.format( set=self.fields.set ), @@ -79,7 +81,7 @@ class BrickSet(RebrickableSet): )) # Complete - self.socket.complete( + socket.complete( message='Set {set}: imported (Go to the set)'.format( # noqa: E501 set=self.fields.set, url=self.url() @@ -88,7 +90,7 @@ class BrickSet(RebrickableSet): ) except Exception as e: - self.socket.fail( + socket.fail( message='Error while importing set {set}: {error}'.format( set=self.fields.set, error=e, diff --git a/bricktracker/socket.py b/bricktracker/socket.py index 3fbd6cd..7aedaf2 100644 --- a/bricktracker/socket.py +++ b/bricktracker/socket.py @@ -109,11 +109,11 @@ class BrickSocket(object): @self.socket.on(MESSAGES['IMPORT_SET'], namespace=self.namespace) @rebrickable_socket(self) def import_set(data: dict[str, Any], /) -> None: - BrickSet(socket=self).download(data) + BrickSet().download(self, data) @self.socket.on(MESSAGES['LOAD_SET'], namespace=self.namespace) def load_set(data: dict[str, Any], /) -> None: - BrickSet(socket=self).load(data) + BrickSet().load(self, data) # Update the progress auto-incrementing def auto_progress( -- 2.45.2 From ebf291b7b8d463af0b9c579cd829efd6bc2fdc02 Mon Sep 17 00:00:00 2001 From: Gregoo Date: Mon, 27 Jan 2025 17:07:30 +0100 Subject: [PATCH 09/19] Rename load to from_set for clarity --- bricktracker/minifigure_list.py | 2 +- bricktracker/set.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/bricktracker/minifigure_list.py b/bricktracker/minifigure_list.py index 04ece73..4b5f183 100644 --- a/bricktracker/minifigure_list.py +++ b/bricktracker/minifigure_list.py @@ -61,7 +61,7 @@ class BrickMinifigureList(BrickRecordList[BrickMinifigure]): return self # Load minifigures from a brickset - def load(self, brickset: 'BrickSet', /) -> Self: + def from_set(self, brickset: 'BrickSet', /) -> Self: # Save the brickset self.brickset = brickset diff --git a/bricktracker/set.py b/bricktracker/set.py index e521268..17f71b8 100644 --- a/bricktracker/set.py +++ b/bricktracker/set.py @@ -101,7 +101,7 @@ class BrickSet(RebrickableSet): # Minifigures def minifigures(self, /) -> BrickMinifigureList: - return BrickMinifigureList().load(self) + return BrickMinifigureList().from_set(self) # Parts def parts(self, /) -> BrickPartList: -- 2.45.2 From 1519f33e98ab1399487e14c3b10f1c52c907f36f Mon Sep 17 00:00:00 2001 From: Gregoo Date: Mon, 27 Jan 2025 18:39:35 +0100 Subject: [PATCH 10/19] Deduplicate minifigures --- .env.sample | 11 +- bricktracker/config.py | 4 +- bricktracker/minifigure.py | 161 ++++++------------ bricktracker/minifigure_list.py | 66 +++++-- bricktracker/part.py | 8 +- bricktracker/part_list.py | 2 +- bricktracker/rebrickable_image.py | 14 +- bricktracker/rebrickable_minifigure.py | 130 ++++++++++++++ bricktracker/rebrickable_minifigures.py | 85 --------- bricktracker/rebrickable_parts.py | 2 +- bricktracker/set.py | 5 +- bricktracker/set_list.py | 16 +- bricktracker/sql/migrations/0007.sql | 30 ++++ bricktracker/sql/migrations/0008.sql | 32 ++++ .../minifigure/base/{select.sql => base.sql} | 17 +- bricktracker/sql/minifigure/insert.sql | 20 +-- bricktracker/sql/minifigure/list/all.sql | 12 +- bricktracker/sql/minifigure/list/from_set.sql | 5 +- bricktracker/sql/minifigure/list/last.sql | 10 +- .../sql/minifigure/list/missing_part.sql | 10 +- .../sql/minifigure/list/using_part.sql | 8 +- .../sql/minifigure/select/generic.sql | 28 +-- .../sql/minifigure/select/specific.sql | 7 +- bricktracker/sql/part/list/all.sql | 15 +- bricktracker/sql/part/list/missing.sql | 10 +- bricktracker/sql/part/select/generic.sql | 10 +- .../sql/rebrickable/minifigure/insert.sql | 11 ++ .../sql/rebrickable/minifigure/list.sql | 6 + .../sql/rebrickable/minifigure/select.sql | 8 + bricktracker/sql/schema/drop.sql | 3 + bricktracker/sql/set/base/full.sql | 10 +- bricktracker/sql/set/delete/set.sql | 4 +- .../sql/set/list/missing_minifigure.sql | 2 +- .../sql/set/list/using_minifigure.sql | 2 +- bricktracker/sql/set/select/full.sql | 2 +- bricktracker/sql_counter.py | 4 +- bricktracker/views/set.py | 2 +- templates/minifigure/card.html | 8 +- templates/minifigure/table.html | 2 +- templates/set/card.html | 2 +- 40 files changed, 441 insertions(+), 343 deletions(-) create mode 100644 bricktracker/rebrickable_minifigure.py delete mode 100644 bricktracker/rebrickable_minifigures.py create mode 100644 bricktracker/sql/migrations/0007.sql create mode 100644 bricktracker/sql/migrations/0008.sql rename bricktracker/sql/minifigure/base/{select.sql => base.sql} (56%) create mode 100644 bricktracker/sql/rebrickable/minifigure/insert.sql create mode 100644 bricktracker/sql/rebrickable/minifigure/list.sql create mode 100644 bricktracker/sql/rebrickable/minifigure/select.sql diff --git a/.env.sample b/.env.sample index 06584db..91caf76 100644 --- a/.env.sample +++ b/.env.sample @@ -121,10 +121,11 @@ # Optional: Change the default order of minifigures. By default ordered by insertion order. # Useful column names for this option are: -# - "minifigures"."fig_num": minifigure ID (fig-xxxxx) -# - "minifigures"."name": minifigure name -# Default: "minifigures"."name" ASC -# BK_MINIFIGURES_DEFAULT_ORDER="minifigures"."name" ASC +# - "rebrickable_minifigures"."figure": minifigure ID (fig-xxxxx) +# - "rebrickable_minifigures"."number": minifigure ID as an integer (xxxxx) +# - "rebrickable_minifigures"."name": minifigure name +# Default: "rebrickable_minifigures"."name" ASC +# BK_MINIFIGURES_DEFAULT_ORDER="rebrickable_minifigures"."name" ASC # Optional: Folder where to store the minifigures images, relative to the '/app/static/' folder # Default: minifigs @@ -175,7 +176,7 @@ # BK_REBRICKABLE_IMAGE_NIL_MINIFIGURE= # Optional: Pattern of the link to Rebrickable for a minifigure. Will be passed to Python .format() -# Default: https://rebrickable.com/minifigs/{number} +# Default: https://rebrickable.com/minifigs/{figure} # BK_REBRICKABLE_LINK_MINIFIGURE_PATTERN= # Optional: Pattern of the link to Rebrickable for a part. Will be passed to Python .format() diff --git a/bricktracker/config.py b/bricktracker/config.py index f9d8f2b..236eb54 100644 --- a/bricktracker/config.py +++ b/bricktracker/config.py @@ -32,7 +32,7 @@ CONFIG: Final[list[dict[str, Any]]] = [ {'n': 'HIDE_MISSING_PARTS', 'c': bool}, {'n': 'HIDE_SET_INSTRUCTIONS', 'c': bool}, {'n': 'HIDE_WISHES', 'c': bool}, - {'n': 'MINIFIGURES_DEFAULT_ORDER', 'd': '"minifigures"."name" ASC'}, + {'n': 'MINIFIGURES_DEFAULT_ORDER', 'd': '"rebrickable_minifigures"."name" ASC'}, # noqa: E501 {'n': 'MINIFIGURES_FOLDER', 'd': 'minifigs', 's': True}, {'n': 'NO_THREADED_SOCKET', 'c': bool}, {'n': 'PARTS_DEFAULT_ORDER', 'd': '"inventory"."name" ASC, "inventory"."color_name" ASC, "inventory"."is_spare" ASC'}, # noqa: E501 @@ -42,7 +42,7 @@ CONFIG: Final[list[dict[str, Any]]] = [ {'n': 'REBRICKABLE_API_KEY', 'e': 'REBRICKABLE_API_KEY', 'd': ''}, {'n': 'REBRICKABLE_IMAGE_NIL', 'd': 'https://rebrickable.com/static/img/nil.png'}, # noqa: E501 {'n': 'REBRICKABLE_IMAGE_NIL_MINIFIGURE', 'd': 'https://rebrickable.com/static/img/nil_mf.jpg'}, # noqa: E501 - {'n': 'REBRICKABLE_LINK_MINIFIGURE_PATTERN', 'd': 'https://rebrickable.com/minifigs/{number}'}, # noqa: E501 + {'n': 'REBRICKABLE_LINK_MINIFIGURE_PATTERN', 'd': 'https://rebrickable.com/minifigs/{figure}'}, # noqa: E501 {'n': 'REBRICKABLE_LINK_PART_PATTERN', 'd': 'https://rebrickable.com/parts/{number}/_/{color}'}, # noqa: E501 {'n': 'REBRICKABLE_LINK_INSTRUCTIONS_PATTERN', 'd': 'https://rebrickable.com/instructions/{path}'}, # noqa: E501 {'n': 'REBRICKABLE_USER_AGENT', 'd': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.124 Safari/537.36'}, # noqa: E501 diff --git a/bricktracker/minifigure.py b/bricktracker/minifigure.py index 0ad55b1..76a482e 100644 --- a/bricktracker/minifigure.py +++ b/bricktracker/minifigure.py @@ -1,48 +1,61 @@ -from sqlite3 import Row -from typing import Any, Self, TYPE_CHECKING - -from flask import current_app, url_for +import logging +import traceback +from typing import Self, TYPE_CHECKING from .exceptions import ErrorException, NotFoundException from .part_list import BrickPartList -from .rebrickable_image import RebrickableImage -from .record import BrickRecord +from .rebrickable_parts import RebrickableParts +from .rebrickable_minifigure import RebrickableMinifigure if TYPE_CHECKING: from .set import BrickSet + from .socket import BrickSocket + +logger = logging.getLogger(__name__) # Lego minifigure -class BrickMinifigure(BrickRecord): - brickset: 'BrickSet | None' - +class BrickMinifigure(RebrickableMinifigure): # Queries insert_query: str = 'minifigure/insert' generic_query: str = 'minifigure/select/generic' select_query: str = 'minifigure/select/specific' - def __init__( - self, - /, - *, - brickset: 'BrickSet | None' = None, - record: Row | dict[str, Any] | None = None, - ): - super().__init__() + def download(self, socket: 'BrickSocket'): + if self.brickset is None: + raise ErrorException('Importing a minifigure from Rebrickable outside of a set is not supported') # noqa: E501 - # Save the brickset - self.brickset = brickset + try: + # Insert into the database + socket.auto_progress( + message='Set {set}: inserting minifigure {figure} into database'.format( # noqa: E501 + set=self.brickset.fields.set, + figure=self.fields.figure + ) + ) - # Ingest the record if it has one - if record is not None: - self.ingest(record) + # Insert into database + self.insert(commit=False) - # Return the number just in digits format - def clean_number(self, /) -> str: - number: str = self.fields.fig_num - number = number.removeprefix('fig-') - number = number.lstrip('0') + # Insert the rebrickable set into database + self.insert_rebrickable() - return number + # Load the inventory + RebrickableParts( + socket, + self.brickset, + minifigure=self, + ).download() + + except Exception as e: + socket.fail( + message='Error while importing minifigure {figure} from {set}: {error}'.format( # noqa: E501 + figure=self.fields.figure, + set=self.brickset.fields.set, + error=e, + ) + ) + + logger.debug(traceback.format_exc()) # Parts def generic_parts(self, /) -> BrickPartList: @@ -51,108 +64,38 @@ class BrickMinifigure(BrickRecord): # Parts def parts(self, /) -> BrickPartList: if self.brickset is None: - raise ErrorException('Part list for minifigure {number} requires a brickset'.format( # noqa: E501 - number=self.fields.fig_num, + raise ErrorException('Part list for minifigure {figure} requires a brickset'.format( # noqa: E501 + figure=self.fields.figure, )) return BrickPartList().load(self.brickset, minifigure=self) # Select a generic minifigure - def select_generic(self, fig_num: str, /) -> Self: + def select_generic(self, figure: str, /) -> Self: # Save the parameters to the fields - self.fields.fig_num = fig_num + self.fields.figure = figure if not self.select(override_query=self.generic_query): raise NotFoundException( - 'Minifigure with number {number} was not found in the database'.format( # noqa: E501 - number=self.fields.fig_num, + 'Minifigure with figure {figure} was not found in the database'.format( # noqa: E501 + figure=self.fields.figure, ), ) return self - # Select a specific minifigure (with a set and an number) - def select_specific(self, brickset: 'BrickSet', fig_num: str, /) -> Self: + # Select a specific minifigure (with a set and a figure) + def select_specific(self, brickset: 'BrickSet', figure: str, /) -> Self: # Save the parameters to the fields self.brickset = brickset - self.fields.fig_num = fig_num + self.fields.figure = figure if not self.select(): raise NotFoundException( - 'Minifigure with number {number} from set {set} was not found in the database'.format( # noqa: E501 - number=self.fields.fig_num, + 'Minifigure with figure {figure} from set {set} was not found in the database'.format( # noqa: E501 + figure=self.fields.figure, set=self.brickset.fields.set, ), ) return self - - # Return a dict with common SQL parameters for a minifigure - def sql_parameters(self, /) -> dict[str, Any]: - parameters = super().sql_parameters() - - # Supplement from the brickset - if self.brickset is not None: - if 'u_id' not in parameters: - parameters['u_id'] = self.brickset.fields.id - - if 'set_num' not in parameters: - parameters['set_num'] = self.brickset.fields.set - - return parameters - - # Self url - def url(self, /) -> str: - return url_for( - 'minifigure.details', - number=self.fields.fig_num, - ) - - # Compute the url for minifigure part image - def url_for_image(self, /) -> str: - if not current_app.config['USE_REMOTE_IMAGES']: - if self.fields.set_img_url is None: - file = RebrickableImage.nil_minifigure_name() - else: - file = self.fields.fig_num - - return RebrickableImage.static_url(file, 'MINIFIGURES_FOLDER') - else: - if self.fields.set_img_url is None: - return current_app.config['REBRICKABLE_IMAGE_NIL_MINIFIGURE'] - else: - return self.fields.set_img_url - - # Compute the url for the rebrickable page - def url_for_rebrickable(self, /) -> str: - if current_app.config['REBRICKABLE_LINKS']: - try: - return current_app.config['REBRICKABLE_LINK_MINIFIGURE_PATTERN'].format( # noqa: E501 - number=self.fields.fig_num.lower(), - ) - except Exception: - pass - - return '' - - # Normalize from Rebrickable - @staticmethod - def from_rebrickable( - data: dict[str, Any], - /, - *, - brickset: 'BrickSet | None' = None, - **_, - ) -> dict[str, Any]: - record = { - 'fig_num': data['set_num'], - 'name': data['set_name'], - 'quantity': data['quantity'], - 'set_img_url': data['set_img_url'], - } - - if brickset is not None: - record['set_num'] = brickset.fields.set - record['u_id'] = brickset.fields.id - - return record diff --git a/bricktracker/minifigure_list.py b/bricktracker/minifigure_list.py index 4b5f183..81affa6 100644 --- a/bricktracker/minifigure_list.py +++ b/bricktracker/minifigure_list.py @@ -1,11 +1,17 @@ +import logging +import traceback from typing import Any, Self, TYPE_CHECKING from flask import current_app from .minifigure import BrickMinifigure +from .rebrickable import Rebrickable from .record_list import BrickRecordList if TYPE_CHECKING: from .set import BrickSet + from .socket import BrickSocket + +logger = logging.getLogger(__name__) # Lego minifigures @@ -47,7 +53,7 @@ class BrickMinifigureList(BrickRecordList[BrickMinifigure]): if current_app.config['RANDOM']: order = 'RANDOM()' else: - order = 'minifigures.rowid DESC' + order = '"bricktracker_minifigures"."rowid" DESC' for record in self.select( override_query=self.last_query, @@ -73,16 +79,6 @@ class BrickMinifigureList(BrickRecordList[BrickMinifigure]): return self - # Return a dict with common SQL parameters for a minifigures list - def sql_parameters(self, /) -> dict[str, Any]: - parameters: dict[str, Any] = super().sql_parameters() - - if self.brickset is not None: - parameters['u_id'] = self.brickset.fields.id - parameters['set_num'] = self.brickset.fields.set - - return parameters - # Minifigures missing a part def missing_part( self, @@ -132,3 +128,51 @@ class BrickMinifigureList(BrickRecordList[BrickMinifigure]): self.records.append(minifigure) return self + + # Return a dict with common SQL parameters for a minifigures list + def sql_parameters(self, /) -> dict[str, Any]: + parameters: dict[str, Any] = super().sql_parameters() + + if self.brickset is not None: + parameters['bricktracker_set_id'] = self.brickset.fields.id + + return parameters + + # Import the minifigures from Rebrickable + @staticmethod + def download(socket: 'BrickSocket', brickset: 'BrickSet', /) -> None: + try: + socket.auto_progress( + message='Set {set}: loading minifigures from Rebrickable'.format( # noqa: E501 + set=brickset.fields.set, + ), + increment_total=True, + ) + + logger.debug('rebrick.lego.get_set_minifigs("{set}")'.format( + set=brickset.fields.set, + )) + + minifigures = Rebrickable[BrickMinifigure]( + 'get_set_minifigs', + brickset.fields.set, + BrickMinifigure, + socket=socket, + brickset=brickset, + ).list() + + # Process each minifigure + socket.update_total(len(minifigures), add=True) + + for minifigure in minifigures: + minifigure.download(socket) + + except Exception as e: + socket.fail( + message='Error while importing set {set} minifigure list: {error}'.format( # noqa: E501 + set=brickset.fields.set, + error=e, + ) + ) + + logger.debug(traceback.format_exc()) diff --git a/bricktracker/part.py b/bricktracker/part.py index 80a51bd..b6dc153 100644 --- a/bricktracker/part.py +++ b/bricktracker/part.py @@ -137,7 +137,7 @@ class BrickPart(BrickRecord): if 'set_num' not in parameters: if self.minifigure is not None: - parameters['set_num'] = self.minifigure.fields.fig_num + parameters['set_num'] = self.minifigure.fields.figure elif self.brickset is not None: parameters['set_num'] = self.brickset.fields.set @@ -215,14 +215,14 @@ class BrickPart(BrickRecord): return url_for( 'set.missing_minifigure_part', id=self.fields.u_id, - minifigure_id=self.minifigure.fields.fig_num, - part_id=self.fields.id, + figure=self.minifigure.fields.figure, + part=self.fields.id, ) return url_for( 'set.missing_part', id=self.fields.u_id, - part_id=self.fields.id + part=self.fields.id ) # Compute the url for the rebrickable page diff --git a/bricktracker/part_list.py b/bricktracker/part_list.py index 93897f8..7805d57 100644 --- a/bricktracker/part_list.py +++ b/bricktracker/part_list.py @@ -120,7 +120,7 @@ class BrickPartList(BrickRecordList[BrickPart]): # Use the minifigure number if present, # otherwise use the set number if self.minifigure is not None: - parameters['set_num'] = self.minifigure.fields.fig_num + parameters['set_num'] = self.minifigure.fields.figure elif self.brickset is not None: parameters['set_num'] = self.brickset.fields.set diff --git a/bricktracker/rebrickable_image.py b/bricktracker/rebrickable_image.py index 0a0d9f4..f15a9b4 100644 --- a/bricktracker/rebrickable_image.py +++ b/bricktracker/rebrickable_image.py @@ -8,7 +8,7 @@ from shutil import copyfileobj from .exceptions import DownloadException if TYPE_CHECKING: - from .minifigure import BrickMinifigure + from .rebrickable_minifigure import RebrickableMinifigure from .part import BrickPart from .rebrickable_set import RebrickableSet @@ -16,7 +16,7 @@ if TYPE_CHECKING: # A set, part or minifigure image from Rebrickable class RebrickableImage(object): set: 'RebrickableSet' - minifigure: 'BrickMinifigure | None' + minifigure: 'RebrickableMinifigure | None' part: 'BrickPart | None' extension: str | None @@ -26,7 +26,7 @@ class RebrickableImage(object): set: 'RebrickableSet', /, *, - minifigure: 'BrickMinifigure | None' = None, + minifigure: 'RebrickableMinifigure | None' = None, part: 'BrickPart | None' = None, ): # Save all objects @@ -87,10 +87,10 @@ class RebrickableImage(object): return self.part.fields.part_img_url_id if self.minifigure is not None: - if self.minifigure.fields.set_img_url is None: + if self.minifigure.fields.image is None: return RebrickableImage.nil_minifigure_name() else: - return self.minifigure.fields.fig_num + return self.minifigure.fields.figure return self.set.fields.set @@ -111,10 +111,10 @@ class RebrickableImage(object): return self.part.fields.part_img_url if self.minifigure is not None: - if self.minifigure.fields.set_img_url is None: + if self.minifigure.fields.image is None: return current_app.config['REBRICKABLE_IMAGE_NIL_MINIFIGURE'] else: - return self.minifigure.fields.set_img_url + return self.minifigure.fields.image return self.set.fields.image diff --git a/bricktracker/rebrickable_minifigure.py b/bricktracker/rebrickable_minifigure.py new file mode 100644 index 0000000..28d3d75 --- /dev/null +++ b/bricktracker/rebrickable_minifigure.py @@ -0,0 +1,130 @@ +import logging +from sqlite3 import Row +from typing import Any, TYPE_CHECKING + +from flask import current_app, url_for + +from .exceptions import ErrorException +from .rebrickable_image import RebrickableImage +from .record import BrickRecord +if TYPE_CHECKING: + from .set import BrickSet + from .socket import BrickSocket + +logger = logging.getLogger(__name__) + + +# A minifigure from Rebrickable +class RebrickableMinifigure(BrickRecord): + socket: 'BrickSocket' + brickset: 'BrickSet | None' + + # Queries + select_query: str = 'rebrickable/minifigure/select' + insert_query: str = 'rebrickable/minifigure/insert' + + def __init__( + self, + /, + *, + brickset: 'BrickSet | None' = None, + socket: 'BrickSocket | None' = None, + record: Row | dict[str, Any] | None = None + ): + super().__init__() + + # Placeholders + self.instructions = [] + + # Save the brickset + self.brickset = brickset + + # Save the socket + if socket is not None: + self.socket = socket + + # Ingest the record if it has one + if record is not None: + self.ingest(record) + + # Insert the minifigure from Rebrickable + def insert_rebrickable(self, /) -> bool: + if self.brickset is None: + raise ErrorException('Importing a minifigure from Rebrickable outside of a set is not supported') # noqa: E501 + + # Insert the Rebrickable minifigure to the database + rows, _ = self.insert( + commit=False, + no_defer=True, + override_query=RebrickableMinifigure.insert_query + ) + + inserted = rows > 0 + + if inserted: + if not current_app.config['USE_REMOTE_IMAGES']: + RebrickableImage( + self.brickset, + minifigure=self, + ).download() + + return inserted + + # Return a dict with common SQL parameters for a minifigure + def sql_parameters(self, /) -> dict[str, Any]: + parameters = super().sql_parameters() + + # Supplement from the brickset + if self.brickset is not None: + if 'bricktracker_set_id' not in parameters: + parameters['bricktracker_set_id'] = self.brickset.fields.id + + return parameters + + # Self url + def url(self, /) -> str: + return url_for( + 'minifigure.details', + figure=self.fields.figure, + ) + + # Compute the url for minifigure image + def url_for_image(self, /) -> str: + if not current_app.config['USE_REMOTE_IMAGES']: + if self.fields.image is None: + file = RebrickableImage.nil_minifigure_name() + else: + file = self.fields.figure + + return RebrickableImage.static_url(file, 'MINIFIGURES_FOLDER') + else: + if self.fields.image is None: + return current_app.config['REBRICKABLE_IMAGE_NIL_MINIFIGURE'] + else: + return self.fields.image + + # Compute the url for the rebrickable page + def url_for_rebrickable(self, /) -> str: + if current_app.config['REBRICKABLE_LINKS']: + try: + return current_app.config['REBRICKABLE_LINK_MINIFIGURE_PATTERN'].format( # noqa: E501 + number=self.fields.figure, + ) + except Exception: + pass + + return '' + + # Normalize from Rebrickable + @staticmethod + def from_rebrickable(data: dict[str, Any], /, **_) -> dict[str, Any]: + # Extracting number + number = int(str(data['set_num'])[5:]) + + return { + 'figure': str(data['set_num']), + 'number': int(number), + 'name': str(data['set_name']), + 'quantity': int(data['quantity']), + 'image': data['set_img_url'], + } diff --git a/bricktracker/rebrickable_minifigures.py b/bricktracker/rebrickable_minifigures.py deleted file mode 100644 index eb72e06..0000000 --- a/bricktracker/rebrickable_minifigures.py +++ /dev/null @@ -1,85 +0,0 @@ -import logging -from typing import TYPE_CHECKING - -from flask import current_app - -from .minifigure import BrickMinifigure -from .rebrickable import Rebrickable -from .rebrickable_image import RebrickableImage -from .rebrickable_parts import RebrickableParts -if TYPE_CHECKING: - from .set import BrickSet - from .socket import BrickSocket - -logger = logging.getLogger(__name__) - - -# Minifigures from Rebrickable -class RebrickableMinifigures(object): - socket: 'BrickSocket' - brickset: 'BrickSet' - - def __init__(self, socket: 'BrickSocket', brickset: 'BrickSet', /): - # Save the socket - self.socket = socket - - # Save the objects - self.brickset = brickset - - # Import the minifigures from Rebrickable - def download(self, /) -> None: - self.socket.auto_progress( - message='Set {number}: loading minifigures from Rebrickable'.format( # noqa: E501 - number=self.brickset.fields.set, - ), - increment_total=True, - ) - - logger.debug('rebrick.lego.get_set_minifigs("{set}")'.format( - set=self.brickset.fields.set, - )) - - minifigures = Rebrickable[BrickMinifigure]( - 'get_set_minifigs', - self.brickset.fields.set, - BrickMinifigure, - socket=self.socket, - brickset=self.brickset, - ).list() - - # Process each minifigure - total = len(minifigures) - for index, minifigure in enumerate(minifigures): - # Insert into the database - self.socket.auto_progress( - message='Set {number}: inserting minifigure {current}/{total} into database'.format( # noqa: E501 - number=self.brickset.fields.set, - current=index+1, - total=total, - ) - ) - - # Insert into database - minifigure.insert(commit=False) - - # Grab the image - self.socket.progress( - message='Set {number}: downloading minifigure {current}/{total} image'.format( # noqa: E501 - number=self.brickset.fields.set, - current=index+1, - total=total, - ) - ) - - if not current_app.config['USE_REMOTE_IMAGES']: - RebrickableImage( - self.brickset, - minifigure=minifigure - ).download() - - # Load the inventory - RebrickableParts( - self.socket, - self.brickset, - minifigure=minifigure, - ).download() diff --git a/bricktracker/rebrickable_parts.py b/bricktracker/rebrickable_parts.py index 69c42dc..9fd2341 100644 --- a/bricktracker/rebrickable_parts.py +++ b/bricktracker/rebrickable_parts.py @@ -40,7 +40,7 @@ class RebrickableParts(object): self.minifigure = minifigure if self.minifigure is not None: - self.number = self.minifigure.fields.fig_num + self.number = self.minifigure.fields.figure self.kind = 'Minifigure' self.method = 'get_minifig_elements' else: diff --git a/bricktracker/set.py b/bricktracker/set.py index 17f71b8..52a2ed9 100644 --- a/bricktracker/set.py +++ b/bricktracker/set.py @@ -8,7 +8,6 @@ from flask import current_app, url_for from .exceptions import DatabaseException, NotFoundException from .minifigure_list import BrickMinifigureList from .part_list import BrickPartList -from .rebrickable_minifigures import RebrickableMinifigures from .rebrickable_parts import RebrickableParts from .rebrickable_set import RebrickableSet from .set_checkbox import BrickSetCheckbox @@ -55,14 +54,14 @@ class BrickSet(RebrickableSet): # Insert into database self.insert(commit=False) - # Execute the parent download method + # Insert the rebrickable set into database self.insert_rebrickable() # Load the inventory RebrickableParts(socket, self).download() # Load the minifigures - RebrickableMinifigureList(socket, self).download() + BrickMinifigureList.download(socket, self) # Commit the transaction to the database socket.auto_progress( diff --git a/bricktracker/set_list.py b/bricktracker/set_list.py index 3b229e8..58ae8ec 100644 --- a/bricktracker/set_list.py +++ b/bricktracker/set_list.py @@ -82,13 +82,9 @@ class BrickSetList(BrickRecordList[BrickSet]): return self # Sets missing a minifigure - def missing_minifigure( - self, - fig_num: str, - / - ) -> Self: + def missing_minifigure(self, figure: str, /) -> Self: # Save the parameters to the fields - self.fields.fig_num = fig_num + self.fields.figure = figure # Load the sets from the database for record in self.select( @@ -127,13 +123,9 @@ class BrickSetList(BrickRecordList[BrickSet]): return self # Sets using a minifigure - def using_minifigure( - self, - fig_num: str, - / - ) -> Self: + def using_minifigure(self, figure: str, /) -> Self: # Save the parameters to the fields - self.fields.fig_num = fig_num + self.fields.figure = figure # Load the sets from the database for record in self.select( diff --git a/bricktracker/sql/migrations/0007.sql b/bricktracker/sql/migrations/0007.sql new file mode 100644 index 0000000..09830c4 --- /dev/null +++ b/bricktracker/sql/migrations/0007.sql @@ -0,0 +1,30 @@ +-- description: Creation of the deduplicated table of Rebrickable minifigures + +BEGIN TRANSACTION; + +-- Create a Rebrickable minifigures table: each unique minifigure imported from Rebrickable +CREATE TABLE "rebrickable_minifigures" ( + "figure" TEXT NOT NULL, + "number" INTEGER NOT NULL, + "name" TEXT NOT NULL, + "image" TEXT, + PRIMARY KEY("figure") +); + +-- Insert existing sets into the new table +INSERT INTO "rebrickable_minifigures" ( + "figure", + "number", + "name", + "image" +) +SELECT + "minifigures"."fig_num", + CAST(SUBSTR("minifigures"."fig_num", 5) AS INTEGER), + "minifigures"."name", + "minifigures"."set_img_url" +FROM "minifigures" +GROUP BY + "minifigures"."fig_num"; + +COMMIT; \ No newline at end of file diff --git a/bricktracker/sql/migrations/0008.sql b/bricktracker/sql/migrations/0008.sql new file mode 100644 index 0000000..48b905a --- /dev/null +++ b/bricktracker/sql/migrations/0008.sql @@ -0,0 +1,32 @@ +-- description: Migrate the Bricktracker minifigures + +PRAGMA foreign_keys = ON; + +BEGIN TRANSACTION; + +-- Create a Bricktable minifigures table: an amount of minifigures linked to a Bricktracker set +CREATE TABLE "bricktracker_minifigures" ( + "bricktracker_set_id" TEXT NOT NULL, + "rebrickable_figure" TEXT NOT NULL, + "quantity" INTEGER NOT NULL, + PRIMARY KEY("bricktracker_set_id", "rebrickable_figure"), + FOREIGN KEY("bricktracker_set_id") REFERENCES "bricktracker_sets"("id"), + FOREIGN KEY("rebrickable_figure") REFERENCES "rebrickable_minifigures"("figure") +); + +-- Insert existing sets into the new table +INSERT INTO "bricktracker_minifigures" ( + "bricktracker_set_id", + "rebrickable_figure", + "quantity" +) +SELECT + "minifigures"."u_id", + "minifigures"."fig_num", + "minifigures"."quantity" +FROM "minifigures"; + +-- Rename the original table (don't delete it yet?) +ALTER TABLE "minifigures" RENAME TO "minifigures_old"; + +COMMIT; \ No newline at end of file diff --git a/bricktracker/sql/minifigure/base/select.sql b/bricktracker/sql/minifigure/base/base.sql similarity index 56% rename from bricktracker/sql/minifigure/base/select.sql rename to bricktracker/sql/minifigure/base/base.sql index 8182998..bfaf10d 100644 --- a/bricktracker/sql/minifigure/base/select.sql +++ b/bricktracker/sql/minifigure/base/base.sql @@ -1,10 +1,10 @@ SELECT - "minifigures"."fig_num", - "minifigures"."set_num", - "minifigures"."name", - "minifigures"."quantity", - "minifigures"."set_img_url", - "minifigures"."u_id", + {% block set %}{% endblock %} + "bricktracker_minifigures"."quantity", + "rebrickable_minifigures"."figure", + "rebrickable_minifigures"."number", + "rebrickable_minifigures"."name", + "rebrickable_minifigures"."image", {% block total_missing %} NULL AS "total_missing", -- dummy for order: total_missing {% endblock %} @@ -14,7 +14,10 @@ SELECT {% block total_sets %} NULL AS "total_sets" -- dummy for order: total_sets {% endblock %} -FROM "minifigures" +FROM "bricktracker_minifigures" + +INNER JOIN "rebrickable_minifigures" +ON "bricktracker_minifigures"."rebrickable_figure" IS NOT DISTINCT FROM "rebrickable_minifigures"."figure" {% block join %}{% endblock %} diff --git a/bricktracker/sql/minifigure/insert.sql b/bricktracker/sql/minifigure/insert.sql index d72a2a3..cd7c413 100644 --- a/bricktracker/sql/minifigure/insert.sql +++ b/bricktracker/sql/minifigure/insert.sql @@ -1,15 +1,9 @@ -INSERT INTO "minifigures" ( - "fig_num", - "set_num", - "name", - "quantity", - "set_img_url", - "u_id" +INSERT INTO "bricktracker_minifigures" ( + "bricktracker_set_id", + "rebrickable_figure", + "quantity" ) VALUES ( - :fig_num, - :set_num, - :name, - :quantity, - :set_img_url, - :u_id + :bricktracker_set_id, + :figure, + :quantity ) diff --git a/bricktracker/sql/minifigure/list/all.sql b/bricktracker/sql/minifigure/list/all.sql index a00f474..82e61a2 100644 --- a/bricktracker/sql/minifigure/list/all.sql +++ b/bricktracker/sql/minifigure/list/all.sql @@ -1,15 +1,15 @@ -{% extends 'minifigure/base/select.sql' %} +{% extends 'minifigure/base/base.sql' %} {% block total_missing %} SUM(IFNULL("missing_join"."total", 0)) AS "total_missing", {% endblock %} {% block total_quantity %} -SUM(IFNULL("minifigures"."quantity", 0)) AS "total_quantity", +SUM(IFNULL("bricktracker_minifigures"."quantity", 0)) AS "total_quantity", {% endblock %} {% block total_sets %} -COUNT("minifigures"."set_num") AS "total_sets" +COUNT("bricktracker_minifigures"."bricktracker_set_id") AS "total_sets" {% endblock %} {% block join %} @@ -24,11 +24,11 @@ LEFT JOIN ( "missing"."set_num", "missing"."u_id" ) missing_join -ON "minifigures"."u_id" IS NOT DISTINCT FROM "missing_join"."u_id" -AND "minifigures"."fig_num" IS NOT DISTINCT FROM "missing_join"."set_num" +ON "bricktracker_minifigures"."bricktracker_set_id" IS NOT DISTINCT FROM "missing_join"."u_id" +AND "rebrickable_minifigures"."figure" IS NOT DISTINCT FROM "missing_join"."set_num" {% endblock %} {% block group %} GROUP BY - "minifigures"."fig_num" + "rebrickable_minifigures"."figure" {% endblock %} diff --git a/bricktracker/sql/minifigure/list/from_set.sql b/bricktracker/sql/minifigure/list/from_set.sql index ea2dcbe..65b4e69 100644 --- a/bricktracker/sql/minifigure/list/from_set.sql +++ b/bricktracker/sql/minifigure/list/from_set.sql @@ -1,6 +1,5 @@ -{% extends 'minifigure/base/select.sql' %} +{% extends 'minifigure/base/base.sql' %} {% block where %} -WHERE "minifigures"."u_id" IS NOT DISTINCT FROM :u_id -AND "minifigures"."set_num" IS NOT DISTINCT FROM :set_num +WHERE "bricktracker_minifigures"."bricktracker_set_id" IS NOT DISTINCT FROM :bricktracker_set_id {% endblock %} diff --git a/bricktracker/sql/minifigure/list/last.sql b/bricktracker/sql/minifigure/list/last.sql index faf3f40..266b7c0 100644 --- a/bricktracker/sql/minifigure/list/last.sql +++ b/bricktracker/sql/minifigure/list/last.sql @@ -1,4 +1,4 @@ -{% extends 'minifigure/base/select.sql' %} +{% extends 'minifigure/base/base.sql' %} {% block total_missing %} SUM(IFNULL("missing"."quantity", 0)) AS "total_missing", @@ -6,12 +6,12 @@ SUM(IFNULL("missing"."quantity", 0)) AS "total_missing", {% block join %} LEFT JOIN "missing" -ON "minifigures"."fig_num" IS NOT DISTINCT FROM "missing"."set_num" -AND "minifigures"."u_id" IS NOT DISTINCT FROM "missing"."u_id" +ON "rebrickable_minifigures"."figure" IS NOT DISTINCT FROM "missing"."set_num" +AND "bricktracker_minifigures"."bricktracker_set_id" IS NOT DISTINCT FROM "missing"."u_id" {% endblock %} {% block group %} GROUP BY - "minifigures"."fig_num", - "minifigures"."u_id" + "rebrickable_minifigures"."figure", + "bricktracker_minifigures"."bricktracker_set_id" {% endblock %} diff --git a/bricktracker/sql/minifigure/list/missing_part.sql b/bricktracker/sql/minifigure/list/missing_part.sql index e0bc54d..660da6d 100644 --- a/bricktracker/sql/minifigure/list/missing_part.sql +++ b/bricktracker/sql/minifigure/list/missing_part.sql @@ -1,4 +1,4 @@ -{% extends 'minifigure/base/select.sql' %} +{% extends 'minifigure/base/base.sql' %} {% block total_missing %} SUM(IFNULL("missing"."quantity", 0)) AS "total_missing", @@ -6,12 +6,12 @@ SUM(IFNULL("missing"."quantity", 0)) AS "total_missing", {% block join %} LEFT JOIN "missing" -ON "minifigures"."fig_num" IS NOT DISTINCT FROM "missing"."set_num" -AND "minifigures"."u_id" IS NOT DISTINCT FROM "missing"."u_id" +ON "rebrickable_minifigures"."figure" IS NOT DISTINCT FROM "missing"."set_num" +AND "bricktracker_minifigures"."bricktracker_set_id" IS NOT DISTINCT FROM "missing"."u_id" {% endblock %} {% block where %} -WHERE "minifigures"."fig_num" IN ( +WHERE "rebrickable_minifigures"."figure" IN ( SELECT "missing"."set_num" FROM "missing" @@ -26,5 +26,5 @@ WHERE "minifigures"."fig_num" IN ( {% block group %} GROUP BY - "minifigures"."fig_num" + "rebrickable_minifigures"."figure" {% endblock %} diff --git a/bricktracker/sql/minifigure/list/using_part.sql b/bricktracker/sql/minifigure/list/using_part.sql index c40d379..e701d8d 100644 --- a/bricktracker/sql/minifigure/list/using_part.sql +++ b/bricktracker/sql/minifigure/list/using_part.sql @@ -1,11 +1,11 @@ -{% extends 'minifigure/base/select.sql' %} +{% extends 'minifigure/base/base.sql' %} {% block total_quantity %} -SUM("minifigures"."quantity") AS "total_quantity", +SUM("bricktracker_minifigures"."quantity") AS "total_quantity", {% endblock %} {% block where %} -WHERE "minifigures"."fig_num" IN ( +WHERE "rebrickable_minifigures"."figure" IN ( SELECT "inventory"."set_num" FROM "inventory" @@ -20,5 +20,5 @@ WHERE "minifigures"."fig_num" IN ( {% block group %} GROUP BY - "minifigures"."fig_num" + "rebrickable_minifigures"."figure" {% endblock %} diff --git a/bricktracker/sql/minifigure/select/generic.sql b/bricktracker/sql/minifigure/select/generic.sql index 114810d..966c022 100644 --- a/bricktracker/sql/minifigure/select/generic.sql +++ b/bricktracker/sql/minifigure/select/generic.sql @@ -1,38 +1,28 @@ -{% extends 'minifigure/base/select.sql' %} +{% extends 'minifigure/base/base.sql' %} {% block total_missing %} -SUM(IFNULL("missing_join"."total", 0)) AS "total_missing", +SUM(IFNULL("missing"."quantity", 0)) AS "total_missing", {% endblock %} {% block total_quantity %} -SUM(IFNULL("minifigures"."quantity", 0)) AS "total_quantity", +SUM(IFNULL("bricktracker_minifigures"."quantity", 0)) AS "total_quantity", {% endblock %} {% block total_sets %} -COUNT("minifigures"."set_num") AS "total_sets" +COUNT(DISTINCT "bricktracker_minifigures"."bricktracker_set_id") AS "total_sets" {% endblock %} {% block join %} --- LEFT JOIN + SELECT to avoid messing the total -LEFT JOIN ( - SELECT - "missing"."set_num", - "missing"."u_id", - SUM("missing"."quantity") AS "total" - FROM "missing" - GROUP BY - "missing"."set_num", - "missing"."u_id" -) "missing_join" -ON "minifigures"."u_id" IS NOT DISTINCT FROM "missing_join"."u_id" -AND "minifigures"."fig_num" IS NOT DISTINCT FROM "missing_join"."set_num" +LEFT JOIN "missing" +ON "rebrickable_minifigures"."figure" IS NOT DISTINCT FROM "missing"."set_num" +AND "bricktracker_minifigures"."bricktracker_set_id" IS NOT DISTINCT FROM "missing"."u_id" {% endblock %} {% block where %} -WHERE "minifigures"."fig_num" IS NOT DISTINCT FROM :fig_num +WHERE "rebrickable_minifigures"."figure" IS NOT DISTINCT FROM :figure {% endblock %} {% block group %} GROUP BY - "minifigures"."fig_num" + "rebrickable_minifigures"."figure" {% endblock %} diff --git a/bricktracker/sql/minifigure/select/specific.sql b/bricktracker/sql/minifigure/select/specific.sql index 34a8b3d..479c9e5 100644 --- a/bricktracker/sql/minifigure/select/specific.sql +++ b/bricktracker/sql/minifigure/select/specific.sql @@ -1,7 +1,6 @@ -{% extends 'minifigure/base/select.sql' %} +{% extends 'minifigure/base/base.sql' %} {% block where %} -WHERE "minifigures"."fig_num" IS NOT DISTINCT FROM :fig_num -AND "minifigures"."u_id" IS NOT DISTINCT FROM :u_id -AND "minifigures"."set_num" IS NOT DISTINCT FROM :set_num +WHERE "rebrickable_minifigures"."figure" IS NOT DISTINCT FROM :figure +AND "bricktracker_minifigures"."bricktracker_set_id" IS NOT DISTINCT FROM :bricktracker_set_id {% endblock %} diff --git a/bricktracker/sql/part/list/all.sql b/bricktracker/sql/part/list/all.sql index b1ff2ac..9d73fcc 100644 --- a/bricktracker/sql/part/list/all.sql +++ b/bricktracker/sql/part/list/all.sql @@ -5,15 +5,15 @@ SUM(IFNULL("missing"."quantity", 0)) AS "total_missing", {% endblock %} {% block total_quantity %} -SUM("inventory"."quantity" * IFNULL("minifigures"."quantity", 1)) AS "total_quantity", +SUM("inventory"."quantity" * IFNULL("bricktracker_minifigures"."quantity", 1)) AS "total_quantity", {% endblock %} {% block total_sets %} -COUNT(DISTINCT "bricktracker_sets"."id") AS "total_sets", +COUNT(DISTINCT "bricktracker_minifigures"."bricktracker_set_id") AS "total_sets", {% endblock %} {% block total_minifigures %} -SUM(IFNULL("minifigures"."quantity", 0)) AS "total_minifigures" +SUM(IFNULL("bricktracker_minifigures"."quantity", 0)) AS "total_minifigures" {% endblock %} {% block join %} @@ -25,12 +25,9 @@ AND "inventory"."color_id" IS NOT DISTINCT FROM "missing"."color_id" AND "inventory"."element_id" IS NOT DISTINCT FROM "missing"."element_id" AND "inventory"."u_id" IS NOT DISTINCT FROM "missing"."u_id" -LEFT JOIN "minifigures" -ON "inventory"."set_num" IS NOT DISTINCT FROM "minifigures"."fig_num" -AND "inventory"."u_id" IS NOT DISTINCT FROM "minifigures"."u_id" - -LEFT JOIN "bricktracker_sets" -ON "inventory"."u_id" IS NOT DISTINCT FROM "bricktracker_sets"."id" +LEFT JOIN "bricktracker_minifigures" +ON "inventory"."set_num" IS NOT DISTINCT FROM "bricktracker_minifigures"."rebrickable_figure" +AND "inventory"."u_id" IS NOT DISTINCT FROM "bricktracker_minifigures"."bricktracker_set_id" {% endblock %} {% block group %} diff --git a/bricktracker/sql/part/list/missing.sql b/bricktracker/sql/part/list/missing.sql index 555916f..fc64e25 100644 --- a/bricktracker/sql/part/list/missing.sql +++ b/bricktracker/sql/part/list/missing.sql @@ -5,11 +5,11 @@ SUM(IFNULL("missing"."quantity", 0)) AS "total_missing", {% endblock %} {% block total_sets %} -COUNT("inventory"."u_id") - COUNT("minifigures"."u_id") AS "total_sets", +COUNT("inventory"."u_id") - COUNT("bricktracker_minifigures"."bricktracker_set_id") AS "total_sets", {% endblock %} {% block total_minifigures %} -SUM(IFNULL("minifigures"."quantity", 0)) AS "total_minifigures" +SUM(IFNULL("bricktracker_minifigures"."quantity", 0)) AS "total_minifigures" {% endblock %} {% block join %} @@ -21,9 +21,9 @@ AND "missing"."color_id" IS NOT DISTINCT FROM "inventory"."color_id" AND "missing"."element_id" IS NOT DISTINCT FROM "inventory"."element_id" AND "missing"."u_id" IS NOT DISTINCT FROM "inventory"."u_id" -LEFT JOIN "minifigures" -ON "missing"."set_num" IS NOT DISTINCT FROM "minifigures"."fig_num" -AND "missing"."u_id" IS NOT DISTINCT FROM "minifigures"."u_id" +LEFT JOIN "bricktracker_minifigures" +ON "inventory"."set_num" IS NOT DISTINCT FROM "bricktracker_minifigures"."rebrickable_figure" +AND "inventory"."u_id" IS NOT DISTINCT FROM "bricktracker_minifigures"."bricktracker_set_id" {% endblock %} {% block group %} diff --git a/bricktracker/sql/part/select/generic.sql b/bricktracker/sql/part/select/generic.sql index 4a75b4c..28b32a9 100644 --- a/bricktracker/sql/part/select/generic.sql +++ b/bricktracker/sql/part/select/generic.sql @@ -5,11 +5,11 @@ SUM(IFNULL("missing"."quantity", 0)) AS "total_missing", {% endblock %} {% block total_quantity %} -SUM((NOT "inventory"."is_spare") * "inventory"."quantity" * IFNULL("minifigures"."quantity", 1)) AS "total_quantity", +SUM((NOT "inventory"."is_spare") * "inventory"."quantity" * IFNULL("bricktracker_minifigures"."quantity", 1)) AS "total_quantity", {% endblock %} {% block total_spare %} -SUM("inventory"."is_spare" * "inventory"."quantity" * IFNULL("minifigures"."quantity", 1)) AS "total_spare", +SUM("inventory"."is_spare" * "inventory"."quantity" * IFNULL("bricktracker_minifigures"."quantity", 1)) AS "total_spare", {% endblock %} {% block join %} @@ -21,9 +21,9 @@ AND "inventory"."color_id" IS NOT DISTINCT FROM "missing"."color_id" AND "inventory"."element_id" IS NOT DISTINCT FROM "missing"."element_id" AND "inventory"."u_id" IS NOT DISTINCT FROM "missing"."u_id" -LEFT JOIN "minifigures" -ON "inventory"."set_num" IS NOT DISTINCT FROM "minifigures"."fig_num" -AND "inventory"."u_id" IS NOT DISTINCT FROM "minifigures"."u_id" +LEFT JOIN "bricktracker_minifigures" +ON "inventory"."set_num" IS NOT DISTINCT FROM "bricktracker_minifigures"."rebrickable_figure" +AND "inventory"."u_id" IS NOT DISTINCT FROM "bricktracker_minifigures"."bricktracker_set_id" {% endblock %} {% block where %} diff --git a/bricktracker/sql/rebrickable/minifigure/insert.sql b/bricktracker/sql/rebrickable/minifigure/insert.sql new file mode 100644 index 0000000..0671925 --- /dev/null +++ b/bricktracker/sql/rebrickable/minifigure/insert.sql @@ -0,0 +1,11 @@ +INSERT OR IGNORE INTO "rebrickable_minifigures" ( + "figure", + "number", + "name", + "image" +) VALUES ( + :figure, + :number, + :name, + :image +) diff --git a/bricktracker/sql/rebrickable/minifigure/list.sql b/bricktracker/sql/rebrickable/minifigure/list.sql new file mode 100644 index 0000000..ec379d8 --- /dev/null +++ b/bricktracker/sql/rebrickable/minifigure/list.sql @@ -0,0 +1,6 @@ +SELECT + "rebrickable_minifigures"."figure", + "rebrickable_minifigures"."number", + "rebrickable_minifigures"."name", + "rebrickable_minifigures"."image" +FROM "rebrickable_minifigures" diff --git a/bricktracker/sql/rebrickable/minifigure/select.sql b/bricktracker/sql/rebrickable/minifigure/select.sql new file mode 100644 index 0000000..f1c68c1 --- /dev/null +++ b/bricktracker/sql/rebrickable/minifigure/select.sql @@ -0,0 +1,8 @@ +SELECT + "rebrickable_minifigures"."figure", + "rebrickable_minifigures"."number", + "rebrickable_minifigures"."name", + "rebrickable_minifigures"."image" +FROM "rebrickable_minifigures" + +WHERE "rebrickable_minifigures"."figure" IS NOT DISTINCT FROM :figure diff --git a/bricktracker/sql/schema/drop.sql b/bricktracker/sql/schema/drop.sql index b961b28..1d39d99 100644 --- a/bricktracker/sql/schema/drop.sql +++ b/bricktracker/sql/schema/drop.sql @@ -1,12 +1,15 @@ BEGIN transaction; +DROP TABLE IF EXISTS "bricktracker_minifigures"; 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_wishes"; DROP TABLE IF EXISTS "inventory"; DROP TABLE IF EXISTS "minifigures"; +DROP TABLE IF EXISTS "minifigures_old"; DROP TABLE IF EXISTS "missing"; +DROP TABLE IF EXISTS "rebrickable_minifigures"; DROP TABLE IF EXISTS "rebrickable_sets"; DROP TABLE IF EXISTS "sets"; DROP TABLE IF EXISTS "sets_old"; diff --git a/bricktracker/sql/set/base/full.sql b/bricktracker/sql/set/base/full.sql index c169c7a..68333c2 100644 --- a/bricktracker/sql/set/base/full.sql +++ b/bricktracker/sql/set/base/full.sql @@ -32,11 +32,11 @@ ON "bricktracker_sets"."id" IS NOT DISTINCT FROM "missing_join"."u_id" -- LEFT JOIN + SELECT to avoid messing the total LEFT JOIN ( SELECT - "minifigures"."u_id", - SUM("minifigures"."quantity") AS "total" - FROM "minifigures" + "bricktracker_minifigures"."bricktracker_set_id", + SUM("bricktracker_minifigures"."quantity") AS "total" + FROM "bricktracker_minifigures" {% block where_minifigures %}{% endblock %} - GROUP BY "u_id" + GROUP BY "bricktracker_minifigures"."bricktracker_set_id" ) "minifigures_join" -ON "bricktracker_sets"."id" IS NOT DISTINCT FROM "minifigures_join"."u_id" +ON "bricktracker_sets"."id" IS NOT DISTINCT FROM "minifigures_join"."bricktracker_set_id" {% endblock %} \ No newline at end of file diff --git a/bricktracker/sql/set/delete/set.sql b/bricktracker/sql/set/delete/set.sql index dd2c856..93a51df 100644 --- a/bricktracker/sql/set/delete/set.sql +++ b/bricktracker/sql/set/delete/set.sql @@ -9,8 +9,8 @@ WHERE "bricktracker_sets"."id" IS NOT DISTINCT FROM '{{ id }}'; DELETE FROM "bricktracker_set_statuses" WHERE "bricktracker_set_statuses"."bricktracker_set_id" IS NOT DISTINCT FROM '{{ id }}'; -DELETE FROM "minifigures" -WHERE "minifigures"."u_id" IS NOT DISTINCT FROM '{{ id }}'; +DELETE FROM "bricktracker_minifigures" +WHERE "bricktracker_minifigures"."bricktracker_set_id" IS NOT DISTINCT FROM '{{ id }}'; DELETE FROM "missing" WHERE "missing"."u_id" IS NOT DISTINCT FROM '{{ id }}'; diff --git a/bricktracker/sql/set/list/missing_minifigure.sql b/bricktracker/sql/set/list/missing_minifigure.sql index 5f27088..2f19bfe 100644 --- a/bricktracker/sql/set/list/missing_minifigure.sql +++ b/bricktracker/sql/set/list/missing_minifigure.sql @@ -6,7 +6,7 @@ WHERE "bricktracker_sets"."id" IN ( "missing"."u_id" FROM "missing" - WHERE "missing"."set_num" IS NOT DISTINCT FROM :fig_num + WHERE "missing"."set_num" IS NOT DISTINCT FROM :figure GROUP BY "missing"."u_id" ) diff --git a/bricktracker/sql/set/list/using_minifigure.sql b/bricktracker/sql/set/list/using_minifigure.sql index f08a5d7..711866b 100644 --- a/bricktracker/sql/set/list/using_minifigure.sql +++ b/bricktracker/sql/set/list/using_minifigure.sql @@ -6,7 +6,7 @@ WHERE "bricktracker_sets"."id" IN ( "inventory"."u_id" FROM "inventory" - WHERE "inventory"."set_num" IS NOT DISTINCT FROM :fig_num + WHERE "inventory"."set_num" IS NOT DISTINCT FROM :figure GROUP BY "inventory"."u_id" ) diff --git a/bricktracker/sql/set/select/full.sql b/bricktracker/sql/set/select/full.sql index 4b19136..a89d2c9 100644 --- a/bricktracker/sql/set/select/full.sql +++ b/bricktracker/sql/set/select/full.sql @@ -5,7 +5,7 @@ WHERE "missing"."u_id" IS NOT DISTINCT FROM :id {% endblock %} {% block where_minifigures %} -WHERE "minifigures"."u_id" IS NOT DISTINCT FROM :id +WHERE "bricktracker_minifigures"."bricktracker_set_id" IS NOT DISTINCT FROM :id {% endblock %} {% block where %} diff --git a/bricktracker/sql_counter.py b/bricktracker/sql_counter.py index 7175494..d01546f 100644 --- a/bricktracker/sql_counter.py +++ b/bricktracker/sql_counter.py @@ -2,13 +2,15 @@ from typing import Tuple # Some table aliases to make it look cleaner (id: (name, icon)) ALIASES: dict[str, Tuple[str, str]] = { - 'bricktracker_set_checkboxes': ('Checkboxes', 'checkbox-line'), + 'bricktracker_minifigures': ('Bricktracker minifigures', 'group-line'), 'bricktracker_set_statuses': ('Bricktracker sets status', 'checkbox-line'), 'bricktracker_sets': ('Bricktracker sets', 'hashtag'), 'bricktracker_wishes': ('Bricktracker wishes', 'gift-line'), 'inventory': ('Parts', 'shapes-line'), 'minifigures': ('Minifigures', 'group-line'), + 'minifigures_old': ('Minifigures (legacy)', 'group-line'), 'missing': ('Missing', 'error-warning-line'), + 'rebrickable_minifigures': ('Rebrickable minifigures', 'group-line'), 'rebrickable_sets': ('Rebrickable sets', 'hashtag'), 'sets': ('Sets', 'hashtag'), 'sets_old': ('Sets (legacy)', 'hashtag'), diff --git a/bricktracker/views/set.py b/bricktracker/views/set.py index 02353c1..1683175 100644 --- a/bricktracker/views/set.py +++ b/bricktracker/views/set.py @@ -128,7 +128,7 @@ def missing_minifigure_part(*, id: str, figure: str, part: str) -> Response: logger.info('Set {number} ({id}): updated minifigure ({figure}) part ({part}) missing count to {missing}'.format( # noqa: E501 number=brickset.fields.set, id=brickset.fields.id, - figure=brickminifigure.fields.fig_num, + figure=brickminifigure.fields.figure, part=brickpart.fields.id, missing=missing, )) diff --git a/templates/minifigure/card.html b/templates/minifigure/card.html index 79ed414..b1949ef 100644 --- a/templates/minifigure/card.html +++ b/templates/minifigure/card.html @@ -3,11 +3,11 @@ {% import 'macro/card.html' as card %}
- {{ card.header(item, item.fields.name, solo=solo, number=item.clean_number(), icon='user-line') }} - {{ card.image(item, solo=solo, last=last, caption=item.fields.name, alt=item.fields.fig_num, medium=true) }} + {{ card.header(item, item.fields.name, solo=solo, number=item.fields.number, icon='user-line') }} + {{ card.image(item, solo=solo, last=last, caption=item.fields.name, alt=item.fields.figure, medium=true) }}
{% if last %} - {{ badge.set(item.fields.set_num, solo=solo, last=last, id=item.fields.u_id) }} + {{ badge.set(item.fields.set, solo=solo, last=last, id=item.fields.rebrickable_set_id) }} {{ badge.quantity(item.fields.quantity, solo=solo, last=last) }} {% endif %} {{ badge.quantity(item.fields.total_quantity, solo=solo, last=last) }} @@ -19,7 +19,7 @@
{% if solo %}
- {{ accordion.table(item.generic_parts(), 'Parts', item.fields.fig_num, 'minifigure-details', 'part/table.html', icon='shapes-line', alt=item.fields.fig_num, read_only_missing=read_only_missing)}} + {{ accordion.table(item.generic_parts(), 'Parts', item.fields.figure, 'minifigure-details', 'part/table.html', icon='shapes-line', alt=item.fields.figure, read_only_missing=read_only_missing)}} {{ accordion.cards(using, 'Sets using this minifigure', 'using-inventory', 'minifigure-details', 'set/card.html', icon='hashtag') }} {{ accordion.cards(missing, 'Sets missing parts of this minifigure', 'missing-inventory', 'minifigure-details', 'set/card.html', icon='error-warning-line') }}
diff --git a/templates/minifigure/table.html b/templates/minifigure/table.html index 94ccef7..66ece79 100644 --- a/templates/minifigure/table.html +++ b/templates/minifigure/table.html @@ -6,7 +6,7 @@ {% for item in table_collection %} - {{ table.image(item.url_for_image(), caption=item.fields.name, alt=item.fields.fig_num) }} + {{ table.image(item.url_for_image(), caption=item.fields.name, alt=item.fields.figure) }} {{ item.fields.name }} {% if all %} diff --git a/templates/set/card.html b/templates/set/card.html index 1308c71..d6729ee 100644 --- a/templates/set/card.html +++ b/templates/set/card.html @@ -57,7 +57,7 @@ {{ accordion.footer() }} {{ accordion.table(item.parts(), 'Parts', 'parts-inventory', 'set-details', 'part/table.html', icon='shapes-line')}} {% for minifigure in item.minifigures() %} - {{ accordion.table(minifigure.parts(), minifigure.fields.name, minifigure.fields.fig_num, 'set-details', 'part/table.html', quantity=minifigure.fields.quantity, icon='group-line', image=minifigure.url_for_image(), alt=minifigure.fields.fig_num, details=minifigure.url())}} + {{ accordion.table(minifigure.parts(), minifigure.fields.name, minifigure.fields.figure, 'set-details', 'part/table.html', quantity=minifigure.fields.quantity, icon='group-line', image=minifigure.url_for_image(), alt=minifigure.fields.figure, details=minifigure.url())}} {% endfor %} {% endif %} {% if g.login.is_authenticated() %} -- 2.45.2 From cbf2b5b58240153502371abafed41b439a0d1c9e Mon Sep 17 00:00:00 2001 From: Gregoo Date: Mon, 27 Jan 2025 18:40:51 +0100 Subject: [PATCH 11/19] Remove confusing reference to number for sets --- bricktracker/views/set.py | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/bricktracker/views/set.py b/bricktracker/views/set.py index 1683175..3e2304c 100644 --- a/bricktracker/views/set.py +++ b/bricktracker/views/set.py @@ -47,8 +47,8 @@ def update_status(*, id: str, checkbox_id: str) -> Response: brickset.update_status(checkbox, value) # Info - logger.info('Set {number} ({id}): status "{status}" changed to "{state}"'.format( # noqa: E501 - number=brickset.fields.set, + logger.info('Set {set} ({id}): status "{status}" changed to "{state}"'.format( # noqa: E501 + set=brickset.fields.set, id=brickset.fields.id, status=checkbox.fields.name, state=value, @@ -77,8 +77,8 @@ def do_delete(*, id: str) -> Response: brickset.delete() # Info - logger.info('Set {number} ({id}): deleted'.format( - number=brickset.fields.set, + logger.info('Set {set} ({id}): deleted'.format( + set=brickset.fields.set, id=brickset.fields.id, )) @@ -125,8 +125,8 @@ def missing_minifigure_part(*, id: str, figure: str, part: str) -> Response: brickpart.update_missing(missing) # Info - logger.info('Set {number} ({id}): updated minifigure ({figure}) part ({part}) missing count to {missing}'.format( # noqa: E501 - number=brickset.fields.set, + logger.info('Set {set} ({id}): updated minifigure ({figure}) part ({part}) missing count to {missing}'.format( # noqa: E501 + set=brickset.fields.set, id=brickset.fields.id, figure=brickminifigure.fields.figure, part=brickpart.fields.id, @@ -149,8 +149,8 @@ def missing_part(*, id: str, part: str) -> Response: brickpart.update_missing(missing) # Info - logger.info('Set {number} ({id}): updated part ({part}) missing count to {missing}'.format( # noqa: E501 - number=brickset.fields.set, + logger.info('Set {set} ({id}): updated part ({part}) missing count to {missing}'.format( # noqa: E501 + set=brickset.fields.set, id=brickset.fields.id, part=brickpart.fields.id, missing=missing, -- 2.45.2 From 426f0bb1232f931357506a737369d437c0b308f2 Mon Sep 17 00:00:00 2001 From: Gregoo Date: Mon, 27 Jan 2025 18:41:08 +0100 Subject: [PATCH 12/19] Fix hide instructions block placement --- templates/set/card.html | 36 +++++++++++++++++++----------------- 1 file changed, 19 insertions(+), 17 deletions(-) diff --git a/templates/set/card.html b/templates/set/card.html index d6729ee..e9612eb 100644 --- a/templates/set/card.html +++ b/templates/set/card.html @@ -35,26 +35,28 @@ {% endfor %} {% endif %} - {% if solo and not config['HIDE_SET_INSTRUCTIONS'] %} + {% if solo %}
{% if not delete %} - {{ accordion.header('Instructions', 'instructions', 'set-details', expanded=open_instructions, quantity=item.instructions | length, icon='file-line', class='p-0') }} -
- {% if item.instructions | length %} - {% for instruction in item.instructions %} - {{ instruction.filename }} - {% endfor %} - {% else %} - No instructions file found. - {% if g.login.is_authenticated() %} - Upload an instructions file + {% if not config['HIDE_SET_INSTRUCTIONS'] %} + {{ accordion.header('Instructions', 'instructions', 'set-details', expanded=open_instructions, quantity=item.instructions | length, icon='file-line', class='p-0') }} +
+ {% if item.instructions | length %} + {% for instruction in item.instructions %} + {{ instruction.filename }} + {% endfor %} + {% else %} + No instructions file found. + {% if g.login.is_authenticated() %} + Upload an instructions file + {% endif %} {% endif %} - {% endif %} - {% if g.login.is_authenticated() %} - Download instructions from Rebrickable - {% endif %} -
- {{ accordion.footer() }} + {% if g.login.is_authenticated() %} + Download instructions from Rebrickable + {% endif %} +
+ {{ accordion.footer() }} + {% endif %} {{ accordion.table(item.parts(), 'Parts', 'parts-inventory', 'set-details', 'part/table.html', icon='shapes-line')}} {% for minifigure in item.minifigures() %} {{ accordion.table(minifigure.parts(), minifigure.fields.name, minifigure.fields.figure, 'set-details', 'part/table.html', quantity=minifigure.fields.quantity, icon='group-line', image=minifigure.url_for_image(), alt=minifigure.fields.figure, details=minifigure.url())}} -- 2.45.2 From 5a99161e9616bebb34e192aba6366995e084160f Mon Sep 17 00:00:00 2001 From: Gregoo Date: Mon, 27 Jan 2025 18:41:44 +0100 Subject: [PATCH 13/19] Add missing checkboxes counter alias --- bricktracker/sql_counter.py | 1 + 1 file changed, 1 insertion(+) diff --git a/bricktracker/sql_counter.py b/bricktracker/sql_counter.py index d01546f..f2d1cc5 100644 --- a/bricktracker/sql_counter.py +++ b/bricktracker/sql_counter.py @@ -3,6 +3,7 @@ from typing import Tuple # Some table aliases to make it look cleaner (id: (name, icon)) ALIASES: dict[str, Tuple[str, str]] = { 'bricktracker_minifigures': ('Bricktracker minifigures', 'group-line'), + 'bricktracker_set_checkboxes': ('Bricktracker set checkboxes', 'checkbox-line'), # noqa: E501 'bricktracker_set_statuses': ('Bricktracker sets status', 'checkbox-line'), 'bricktracker_sets': ('Bricktracker sets', 'hashtag'), 'bricktracker_wishes': ('Bricktracker wishes', 'gift-line'), -- 2.45.2 From 3c05bfa9fcd6281ac311ecbf2880fe4e6840a4ca Mon Sep 17 00:00:00 2001 From: Gregoo Date: Mon, 27 Jan 2025 18:41:53 +0100 Subject: [PATCH 14/19] Documentation about base SQL files --- .env.sample | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.env.sample b/.env.sample index 91caf76..04e84ee 100644 --- a/.env.sample +++ b/.env.sample @@ -2,7 +2,7 @@ # If set, it will append a direct ORDER BY to the SQL query # while listing objects. You can look at the structure of the SQLite database to # see the schema and the column names. Some fields are compound and not visible -# directly from the schema (joins). You can check the query in the */list.sql files +# directly from the schema (joins). You can check the query in the */list.sql and */base/*.sql files # in the source to see all column names. # The usual syntax for those variables is ""."" [ASC|DESC]. # For composite fields (CASE, SUM, COUNT) the syntax is , there is no
name. -- 2.45.2 From 6b9e1c2cfd321bd80d64943e3415014b8bce88db Mon Sep 17 00:00:00 2001 From: Gregoo Date: Mon, 27 Jan 2025 18:55:26 +0100 Subject: [PATCH 15/19] Add remixicon in the libraries --- docs/development.md | 1 + 1 file changed, 1 insertion(+) diff --git a/docs/development.md b/docs/development.md index 6799be0..8657590 100644 --- a/docs/development.md +++ b/docs/development.md @@ -16,6 +16,7 @@ It uses the following Python/pip packages: It also uses the following libraries and frameworks: - Boostrap (https://getbootstrap.com/) +- Remixicon (https://remixicon.com/) - `baguettebox` (https://github.com/feimosi/baguetteBox.js) - `tinysort` (https://github.com/Sjeiti/TinySort) - `sortable` (https://github.com/tofsjonas/sortable) -- 2.45.2 From 0beb1147b9cc1de3c112ba60a2d5328c69015099 Mon Sep 17 00:00:00 2001 From: Gregoo Date: Mon, 27 Jan 2025 22:23:54 +0100 Subject: [PATCH 16/19] Allow more advanced migration action through a companion python file --- bricktracker/migrations/__init__.py | 0 bricktracker/sql.py | 28 +++++++++++++++++++++++++++- 2 files changed, 27 insertions(+), 1 deletion(-) create mode 100644 bricktracker/migrations/__init__.py diff --git a/bricktracker/migrations/__init__.py b/bricktracker/migrations/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/bricktracker/sql.py b/bricktracker/sql.py index 07811d9..9e47d9a 100644 --- a/bricktracker/sql.py +++ b/bricktracker/sql.py @@ -1,3 +1,4 @@ +from importlib import import_module import logging import os import sqlite3 @@ -301,7 +302,32 @@ class BrickSQL(object): version=pending.version) ) - self.executescript(pending.get_query()) + # Load context from the migrations if it exists + # It looks for a file in migrations/ named after the SQL file + # and containing one function named migration_xxxx, also named + # after the SQL file, returning a context dict. + # + # For instance: + # - sql/migrations/0007.sql + # - migrations/0007.py + # - def migration_0007(BrickSQL) -> dict[str, Any] + try: + module = import_module( + '.migrations.{name}'.format( + name=pending.name + ), + package='bricktracker' + ) + + function = getattr(module, 'migration_{name}'.format( + name=pending.name + )) + + context: dict[str, Any] = function(self) + except Exception: + context: dict[str, Any] = {} + + self.executescript(pending.get_query(), **context) self.execute('schema/set_version', version=pending.version) # Tells whether the database needs upgrade -- 2.45.2 From cf6d5f43c020a4891f8a696d891b0a249e110431 Mon Sep 17 00:00:00 2001 From: Gregoo Date: Mon, 27 Jan 2025 23:07:10 +0100 Subject: [PATCH 17/19] Simplify fields name in the database --- bricktracker/migrations/0007.py | 27 ++++++++ bricktracker/minifigure_list.py | 2 +- bricktracker/rebrickable_minifigure.py | 5 +- bricktracker/sql/migrations/0007.sql | 63 +++++++++++++------ bricktracker/sql/migrations/0008.sql | 40 ++++++------ bricktracker/sql/migrations/0009.sql | 32 ++++++++++ bricktracker/sql/minifigure/base/base.sql | 2 +- bricktracker/sql/minifigure/insert.sql | 6 +- bricktracker/sql/minifigure/list/all.sql | 4 +- bricktracker/sql/minifigure/list/from_set.sql | 2 +- bricktracker/sql/minifigure/list/last.sql | 4 +- .../sql/minifigure/list/missing_part.sql | 2 +- .../sql/minifigure/select/generic.sql | 4 +- .../sql/minifigure/select/specific.sql | 2 +- bricktracker/sql/part/list/all.sql | 6 +- bricktracker/sql/part/list/missing.sql | 6 +- bricktracker/sql/part/select/generic.sql | 4 +- bricktracker/sql/set/base/base.sql | 2 +- bricktracker/sql/set/base/full.sql | 8 +-- bricktracker/sql/set/base/light.sql | 2 +- bricktracker/sql/set/delete/set.sql | 4 +- bricktracker/sql/set/insert.sql | 2 +- bricktracker/sql/set/list/generic.sql | 2 +- bricktracker/sql/set/select/full.sql | 2 +- bricktracker/sql/set/update/status.sql | 6 +- 25 files changed, 159 insertions(+), 80 deletions(-) create mode 100644 bricktracker/migrations/0007.py create mode 100644 bricktracker/sql/migrations/0009.sql diff --git a/bricktracker/migrations/0007.py b/bricktracker/migrations/0007.py new file mode 100644 index 0000000..fb5b723 --- /dev/null +++ b/bricktracker/migrations/0007.py @@ -0,0 +1,27 @@ +from typing import Any, TYPE_CHECKING + +if TYPE_CHECKING: + from ..sql import BrickSQL + + +# Grab the list of checkboxes to create a list of SQL columns +def migration_0007(self: 'BrickSQL') -> dict[str, Any]: + records = self.fetchall('checkbox/list') + + return { + 'sources': ', '.join([ + '"bricktracker_set_statuses_old"."status_{id}"'.format(id=record['id']) # noqa: E501 + for record + in records + ]), + 'targets': ', '.join([ + '"status_{id}"'.format(id=record['id']) + for record + in records + ]), + 'structure': ', '.join([ + '"status_{id}" BOOLEAN NOT NULL DEFAULT 0'.format(id=record['id']) + for record + in records + ]) + } diff --git a/bricktracker/minifigure_list.py b/bricktracker/minifigure_list.py index 81affa6..24b9933 100644 --- a/bricktracker/minifigure_list.py +++ b/bricktracker/minifigure_list.py @@ -134,7 +134,7 @@ class BrickMinifigureList(BrickRecordList[BrickMinifigure]): parameters: dict[str, Any] = super().sql_parameters() if self.brickset is not None: - parameters['bricktracker_set_id'] = self.brickset.fields.id + parameters['id'] = self.brickset.fields.id return parameters diff --git a/bricktracker/rebrickable_minifigure.py b/bricktracker/rebrickable_minifigure.py index 28d3d75..e296356 100644 --- a/bricktracker/rebrickable_minifigure.py +++ b/bricktracker/rebrickable_minifigure.py @@ -75,9 +75,8 @@ class RebrickableMinifigure(BrickRecord): parameters = super().sql_parameters() # Supplement from the brickset - if self.brickset is not None: - if 'bricktracker_set_id' not in parameters: - parameters['bricktracker_set_id'] = self.brickset.fields.id + if self.brickset is not None and 'id' not in parameters: + parameters['id'] = self.brickset.fields.id return parameters diff --git a/bricktracker/sql/migrations/0007.sql b/bricktracker/sql/migrations/0007.sql index 09830c4..036f12e 100644 --- a/bricktracker/sql/migrations/0007.sql +++ b/bricktracker/sql/migrations/0007.sql @@ -1,30 +1,53 @@ --- description: Creation of the deduplicated table of Rebrickable minifigures +-- description: Renaming various complicated field names to something simpler + +PRAGMA foreign_keys = ON; BEGIN TRANSACTION; --- Create a Rebrickable minifigures table: each unique minifigure imported from Rebrickable -CREATE TABLE "rebrickable_minifigures" ( - "figure" TEXT NOT NULL, - "number" INTEGER NOT NULL, - "name" TEXT NOT NULL, - "image" TEXT, - PRIMARY KEY("figure") +-- Rename sets table +ALTER TABLE "bricktracker_sets" RENAME TO "bricktracker_sets_old"; + +-- Re-Create a Bricktable set table with the simplified name +CREATE TABLE "bricktracker_sets" ( + "id" TEXT NOT NULL, + "set" TEXT NOT NULL, + PRIMARY KEY("id"), + FOREIGN KEY("set") REFERENCES "rebrickable_sets"("set") ); -- Insert existing sets into the new table -INSERT INTO "rebrickable_minifigures" ( - "figure", - "number", - "name", - "image" +INSERT INTO "bricktracker_sets" ( + "id", + "set" ) SELECT - "minifigures"."fig_num", - CAST(SUBSTR("minifigures"."fig_num", 5) AS INTEGER), - "minifigures"."name", - "minifigures"."set_img_url" -FROM "minifigures" -GROUP BY - "minifigures"."fig_num"; + "bricktracker_sets_old"."id", + "bricktracker_sets_old"."rebrickable_set" +FROM "bricktracker_sets_old"; + +-- Rename status table +ALTER TABLE "bricktracker_set_statuses" RENAME TO "bricktracker_set_statuses_old"; + +-- Re-create a table for the status of each checkbox +CREATE TABLE "bricktracker_set_statuses" ( + "id" TEXT NOT NULL, + {% if structure %}{{ structure }},{% endif %} + PRIMARY KEY("id"), + FOREIGN KEY("id") REFERENCES "bricktracker_sets"("id") +); + +-- Insert existing status into the new table +INSERT INTO "bricktracker_set_statuses" ( + {% if targets %}{{ targets }},{% endif %} + "id" +) +SELECT + {% if sources %}{{ sources }},{% endif %} + "bricktracker_set_statuses_old"."bricktracker_set_id" +FROM "bricktracker_set_statuses_old"; + +-- Delete the original tables +DROP TABLE "bricktracker_set_statuses_old"; +DROP TABLE "bricktracker_sets_old"; COMMIT; \ No newline at end of file diff --git a/bricktracker/sql/migrations/0008.sql b/bricktracker/sql/migrations/0008.sql index 48b905a..09830c4 100644 --- a/bricktracker/sql/migrations/0008.sql +++ b/bricktracker/sql/migrations/0008.sql @@ -1,32 +1,30 @@ --- description: Migrate the Bricktracker minifigures - -PRAGMA foreign_keys = ON; +-- description: Creation of the deduplicated table of Rebrickable minifigures BEGIN TRANSACTION; --- Create a Bricktable minifigures table: an amount of minifigures linked to a Bricktracker set -CREATE TABLE "bricktracker_minifigures" ( - "bricktracker_set_id" TEXT NOT NULL, - "rebrickable_figure" TEXT NOT NULL, - "quantity" INTEGER NOT NULL, - PRIMARY KEY("bricktracker_set_id", "rebrickable_figure"), - FOREIGN KEY("bricktracker_set_id") REFERENCES "bricktracker_sets"("id"), - FOREIGN KEY("rebrickable_figure") REFERENCES "rebrickable_minifigures"("figure") +-- Create a Rebrickable minifigures table: each unique minifigure imported from Rebrickable +CREATE TABLE "rebrickable_minifigures" ( + "figure" TEXT NOT NULL, + "number" INTEGER NOT NULL, + "name" TEXT NOT NULL, + "image" TEXT, + PRIMARY KEY("figure") ); -- Insert existing sets into the new table -INSERT INTO "bricktracker_minifigures" ( - "bricktracker_set_id", - "rebrickable_figure", - "quantity" +INSERT INTO "rebrickable_minifigures" ( + "figure", + "number", + "name", + "image" ) SELECT - "minifigures"."u_id", "minifigures"."fig_num", - "minifigures"."quantity" -FROM "minifigures"; - --- Rename the original table (don't delete it yet?) -ALTER TABLE "minifigures" RENAME TO "minifigures_old"; + CAST(SUBSTR("minifigures"."fig_num", 5) AS INTEGER), + "minifigures"."name", + "minifigures"."set_img_url" +FROM "minifigures" +GROUP BY + "minifigures"."fig_num"; COMMIT; \ No newline at end of file diff --git a/bricktracker/sql/migrations/0009.sql b/bricktracker/sql/migrations/0009.sql new file mode 100644 index 0000000..135f95d --- /dev/null +++ b/bricktracker/sql/migrations/0009.sql @@ -0,0 +1,32 @@ +-- description: Migrate the Bricktracker minifigures + +PRAGMA foreign_keys = ON; + +BEGIN TRANSACTION; + +-- Create a Bricktable minifigures table: an amount of minifigures linked to a Bricktracker set +CREATE TABLE "bricktracker_minifigures" ( + "id" TEXT NOT NULL, + "figure" TEXT NOT NULL, + "quantity" INTEGER NOT NULL, + PRIMARY KEY("id", "figure"), + FOREIGN KEY("id") REFERENCES "bricktracker_sets"("id"), + FOREIGN KEY("figure") REFERENCES "rebrickable_minifigures"("figure") +); + +-- Insert existing sets into the new table +INSERT INTO "bricktracker_minifigures" ( + "id", + "figure", + "quantity" +) +SELECT + "minifigures"."u_id", + "minifigures"."fig_num", + "minifigures"."quantity" +FROM "minifigures"; + +-- Rename the original table (don't delete it yet?) +ALTER TABLE "minifigures" RENAME TO "minifigures_old"; + +COMMIT; \ No newline at end of file diff --git a/bricktracker/sql/minifigure/base/base.sql b/bricktracker/sql/minifigure/base/base.sql index bfaf10d..c580b38 100644 --- a/bricktracker/sql/minifigure/base/base.sql +++ b/bricktracker/sql/minifigure/base/base.sql @@ -17,7 +17,7 @@ SELECT FROM "bricktracker_minifigures" INNER JOIN "rebrickable_minifigures" -ON "bricktracker_minifigures"."rebrickable_figure" IS NOT DISTINCT FROM "rebrickable_minifigures"."figure" +ON "bricktracker_minifigures"."figure" IS NOT DISTINCT FROM "rebrickable_minifigures"."figure" {% block join %}{% endblock %} diff --git a/bricktracker/sql/minifigure/insert.sql b/bricktracker/sql/minifigure/insert.sql index cd7c413..0a2679e 100644 --- a/bricktracker/sql/minifigure/insert.sql +++ b/bricktracker/sql/minifigure/insert.sql @@ -1,9 +1,9 @@ INSERT INTO "bricktracker_minifigures" ( - "bricktracker_set_id", - "rebrickable_figure", + "id", + "figure", "quantity" ) VALUES ( - :bricktracker_set_id, + :id, :figure, :quantity ) diff --git a/bricktracker/sql/minifigure/list/all.sql b/bricktracker/sql/minifigure/list/all.sql index 82e61a2..498062d 100644 --- a/bricktracker/sql/minifigure/list/all.sql +++ b/bricktracker/sql/minifigure/list/all.sql @@ -9,7 +9,7 @@ SUM(IFNULL("bricktracker_minifigures"."quantity", 0)) AS "total_quantity", {% endblock %} {% block total_sets %} -COUNT("bricktracker_minifigures"."bricktracker_set_id") AS "total_sets" +COUNT("bricktracker_minifigures"."id") AS "total_sets" {% endblock %} {% block join %} @@ -24,7 +24,7 @@ LEFT JOIN ( "missing"."set_num", "missing"."u_id" ) missing_join -ON "bricktracker_minifigures"."bricktracker_set_id" IS NOT DISTINCT FROM "missing_join"."u_id" +ON "bricktracker_minifigures"."id" IS NOT DISTINCT FROM "missing_join"."u_id" AND "rebrickable_minifigures"."figure" IS NOT DISTINCT FROM "missing_join"."set_num" {% endblock %} diff --git a/bricktracker/sql/minifigure/list/from_set.sql b/bricktracker/sql/minifigure/list/from_set.sql index 65b4e69..e22ee95 100644 --- a/bricktracker/sql/minifigure/list/from_set.sql +++ b/bricktracker/sql/minifigure/list/from_set.sql @@ -1,5 +1,5 @@ {% extends 'minifigure/base/base.sql' %} {% block where %} -WHERE "bricktracker_minifigures"."bricktracker_set_id" IS NOT DISTINCT FROM :bricktracker_set_id +WHERE "bricktracker_minifigures"."id" IS NOT DISTINCT FROM :id {% endblock %} diff --git a/bricktracker/sql/minifigure/list/last.sql b/bricktracker/sql/minifigure/list/last.sql index 266b7c0..cacc2f7 100644 --- a/bricktracker/sql/minifigure/list/last.sql +++ b/bricktracker/sql/minifigure/list/last.sql @@ -7,11 +7,11 @@ SUM(IFNULL("missing"."quantity", 0)) AS "total_missing", {% block join %} LEFT JOIN "missing" ON "rebrickable_minifigures"."figure" IS NOT DISTINCT FROM "missing"."set_num" -AND "bricktracker_minifigures"."bricktracker_set_id" IS NOT DISTINCT FROM "missing"."u_id" +AND "bricktracker_minifigures"."id" IS NOT DISTINCT FROM "missing"."u_id" {% endblock %} {% block group %} GROUP BY "rebrickable_minifigures"."figure", - "bricktracker_minifigures"."bricktracker_set_id" + "bricktracker_minifigures"."id" {% endblock %} diff --git a/bricktracker/sql/minifigure/list/missing_part.sql b/bricktracker/sql/minifigure/list/missing_part.sql index 660da6d..3fe4210 100644 --- a/bricktracker/sql/minifigure/list/missing_part.sql +++ b/bricktracker/sql/minifigure/list/missing_part.sql @@ -7,7 +7,7 @@ SUM(IFNULL("missing"."quantity", 0)) AS "total_missing", {% block join %} LEFT JOIN "missing" ON "rebrickable_minifigures"."figure" IS NOT DISTINCT FROM "missing"."set_num" -AND "bricktracker_minifigures"."bricktracker_set_id" IS NOT DISTINCT FROM "missing"."u_id" +AND "bricktracker_minifigures"."id" IS NOT DISTINCT FROM "missing"."u_id" {% endblock %} {% block where %} diff --git a/bricktracker/sql/minifigure/select/generic.sql b/bricktracker/sql/minifigure/select/generic.sql index 966c022..16dc56d 100644 --- a/bricktracker/sql/minifigure/select/generic.sql +++ b/bricktracker/sql/minifigure/select/generic.sql @@ -9,13 +9,13 @@ SUM(IFNULL("bricktracker_minifigures"."quantity", 0)) AS "total_quantity", {% endblock %} {% block total_sets %} -COUNT(DISTINCT "bricktracker_minifigures"."bricktracker_set_id") AS "total_sets" +COUNT(DISTINCT "bricktracker_minifigures"."id") AS "total_sets" {% endblock %} {% block join %} LEFT JOIN "missing" ON "rebrickable_minifigures"."figure" IS NOT DISTINCT FROM "missing"."set_num" -AND "bricktracker_minifigures"."bricktracker_set_id" IS NOT DISTINCT FROM "missing"."u_id" +AND "bricktracker_minifigures"."id" IS NOT DISTINCT FROM "missing"."u_id" {% endblock %} {% block where %} diff --git a/bricktracker/sql/minifigure/select/specific.sql b/bricktracker/sql/minifigure/select/specific.sql index 479c9e5..00a66af 100644 --- a/bricktracker/sql/minifigure/select/specific.sql +++ b/bricktracker/sql/minifigure/select/specific.sql @@ -2,5 +2,5 @@ {% block where %} WHERE "rebrickable_minifigures"."figure" IS NOT DISTINCT FROM :figure -AND "bricktracker_minifigures"."bricktracker_set_id" IS NOT DISTINCT FROM :bricktracker_set_id +AND "bricktracker_minifigures"."id" IS NOT DISTINCT FROM :id {% endblock %} diff --git a/bricktracker/sql/part/list/all.sql b/bricktracker/sql/part/list/all.sql index 9d73fcc..c5bbf69 100644 --- a/bricktracker/sql/part/list/all.sql +++ b/bricktracker/sql/part/list/all.sql @@ -9,7 +9,7 @@ SUM("inventory"."quantity" * IFNULL("bricktracker_minifigures"."quantity", 1)) A {% endblock %} {% block total_sets %} -COUNT(DISTINCT "bricktracker_minifigures"."bricktracker_set_id") AS "total_sets", +COUNT(DISTINCT "bricktracker_minifigures"."id") AS "total_sets", {% endblock %} {% block total_minifigures %} @@ -26,8 +26,8 @@ AND "inventory"."element_id" IS NOT DISTINCT FROM "missing"."element_id" AND "inventory"."u_id" IS NOT DISTINCT FROM "missing"."u_id" LEFT JOIN "bricktracker_minifigures" -ON "inventory"."set_num" IS NOT DISTINCT FROM "bricktracker_minifigures"."rebrickable_figure" -AND "inventory"."u_id" IS NOT DISTINCT FROM "bricktracker_minifigures"."bricktracker_set_id" +ON "inventory"."set_num" IS NOT DISTINCT FROM "bricktracker_minifigures"."figure" +AND "inventory"."u_id" IS NOT DISTINCT FROM "bricktracker_minifigures"."id" {% endblock %} {% block group %} diff --git a/bricktracker/sql/part/list/missing.sql b/bricktracker/sql/part/list/missing.sql index fc64e25..8f17ae3 100644 --- a/bricktracker/sql/part/list/missing.sql +++ b/bricktracker/sql/part/list/missing.sql @@ -5,7 +5,7 @@ SUM(IFNULL("missing"."quantity", 0)) AS "total_missing", {% endblock %} {% block total_sets %} -COUNT("inventory"."u_id") - COUNT("bricktracker_minifigures"."bricktracker_set_id") AS "total_sets", +COUNT("inventory"."u_id") - COUNT("bricktracker_minifigures"."id") AS "total_sets", {% endblock %} {% block total_minifigures %} @@ -22,8 +22,8 @@ AND "missing"."element_id" IS NOT DISTINCT FROM "inventory"."element_id" AND "missing"."u_id" IS NOT DISTINCT FROM "inventory"."u_id" LEFT JOIN "bricktracker_minifigures" -ON "inventory"."set_num" IS NOT DISTINCT FROM "bricktracker_minifigures"."rebrickable_figure" -AND "inventory"."u_id" IS NOT DISTINCT FROM "bricktracker_minifigures"."bricktracker_set_id" +ON "inventory"."set_num" IS NOT DISTINCT FROM "bricktracker_minifigures"."figure" +AND "inventory"."u_id" IS NOT DISTINCT FROM "bricktracker_minifigures"."id" {% endblock %} {% block group %} diff --git a/bricktracker/sql/part/select/generic.sql b/bricktracker/sql/part/select/generic.sql index 28b32a9..eb7e194 100644 --- a/bricktracker/sql/part/select/generic.sql +++ b/bricktracker/sql/part/select/generic.sql @@ -22,8 +22,8 @@ AND "inventory"."element_id" IS NOT DISTINCT FROM "missing"."element_id" AND "inventory"."u_id" IS NOT DISTINCT FROM "missing"."u_id" LEFT JOIN "bricktracker_minifigures" -ON "inventory"."set_num" IS NOT DISTINCT FROM "bricktracker_minifigures"."rebrickable_figure" -AND "inventory"."u_id" IS NOT DISTINCT FROM "bricktracker_minifigures"."bricktracker_set_id" +ON "inventory"."set_num" IS NOT DISTINCT FROM "bricktracker_minifigures"."figure" +AND "inventory"."u_id" IS NOT DISTINCT FROM "bricktracker_minifigures"."id" {% endblock %} {% block where %} diff --git a/bricktracker/sql/set/base/base.sql b/bricktracker/sql/set/base/base.sql index 2f4d683..8b1f4c8 100644 --- a/bricktracker/sql/set/base/base.sql +++ b/bricktracker/sql/set/base/base.sql @@ -21,7 +21,7 @@ SELECT FROM "bricktracker_sets" INNER JOIN "rebrickable_sets" -ON "bricktracker_sets"."rebrickable_set" IS NOT DISTINCT FROM "rebrickable_sets"."set" +ON "bricktracker_sets"."set" IS NOT DISTINCT FROM "rebrickable_sets"."set" {% block join %}{% endblock %} diff --git a/bricktracker/sql/set/base/full.sql b/bricktracker/sql/set/base/full.sql index 68333c2..092e487 100644 --- a/bricktracker/sql/set/base/full.sql +++ b/bricktracker/sql/set/base/full.sql @@ -15,7 +15,7 @@ IFNULL("minifigures_join"."total", 0) AS "total_minifigures" {% block join %} {% if statuses %} LEFT JOIN "bricktracker_set_statuses" -ON "bricktracker_sets"."id" IS NOT DISTINCT FROM "bricktracker_set_statuses"."bricktracker_set_id" +ON "bricktracker_sets"."id" IS NOT DISTINCT FROM "bricktracker_set_statuses"."id" {% endif %} -- LEFT JOIN + SELECT to avoid messing the total @@ -32,11 +32,11 @@ ON "bricktracker_sets"."id" IS NOT DISTINCT FROM "missing_join"."u_id" -- LEFT JOIN + SELECT to avoid messing the total LEFT JOIN ( SELECT - "bricktracker_minifigures"."bricktracker_set_id", + "bricktracker_minifigures"."id", SUM("bricktracker_minifigures"."quantity") AS "total" FROM "bricktracker_minifigures" {% block where_minifigures %}{% endblock %} - GROUP BY "bricktracker_minifigures"."bricktracker_set_id" + GROUP BY "bricktracker_minifigures"."id" ) "minifigures_join" -ON "bricktracker_sets"."id" IS NOT DISTINCT FROM "minifigures_join"."bricktracker_set_id" +ON "bricktracker_sets"."id" IS NOT DISTINCT FROM "minifigures_join"."id" {% endblock %} \ No newline at end of file diff --git a/bricktracker/sql/set/base/light.sql b/bricktracker/sql/set/base/light.sql index b599a87..12df8e2 100644 --- a/bricktracker/sql/set/base/light.sql +++ b/bricktracker/sql/set/base/light.sql @@ -1,6 +1,6 @@ SELECT "bricktracker_sets"."id", - "bricktracker_sets"."rebrickable_set" AS "set" + "bricktracker_sets"."set" FROM "bricktracker_sets" {% block join %}{% endblock %} diff --git a/bricktracker/sql/set/delete/set.sql b/bricktracker/sql/set/delete/set.sql index 93a51df..b477f81 100644 --- a/bricktracker/sql/set/delete/set.sql +++ b/bricktracker/sql/set/delete/set.sql @@ -7,10 +7,10 @@ DELETE FROM "bricktracker_sets" WHERE "bricktracker_sets"."id" IS NOT DISTINCT FROM '{{ id }}'; DELETE FROM "bricktracker_set_statuses" -WHERE "bricktracker_set_statuses"."bricktracker_set_id" IS NOT DISTINCT FROM '{{ id }}'; +WHERE "bricktracker_set_statuses"."id" IS NOT DISTINCT FROM '{{ id }}'; DELETE FROM "bricktracker_minifigures" -WHERE "bricktracker_minifigures"."bricktracker_set_id" IS NOT DISTINCT FROM '{{ id }}'; +WHERE "bricktracker_minifigures"."id" IS NOT DISTINCT FROM '{{ id }}'; DELETE FROM "missing" WHERE "missing"."u_id" IS NOT DISTINCT FROM '{{ id }}'; diff --git a/bricktracker/sql/set/insert.sql b/bricktracker/sql/set/insert.sql index 2462ac5..7dd6dec 100644 --- a/bricktracker/sql/set/insert.sql +++ b/bricktracker/sql/set/insert.sql @@ -1,6 +1,6 @@ INSERT OR IGNORE INTO "bricktracker_sets" ( "id", - "rebrickable_set" + "set" ) VALUES ( :id, :set diff --git a/bricktracker/sql/set/list/generic.sql b/bricktracker/sql/set/list/generic.sql index 0177c2b..d5b2da4 100644 --- a/bricktracker/sql/set/list/generic.sql +++ b/bricktracker/sql/set/list/generic.sql @@ -2,5 +2,5 @@ {% block group %} GROUP BY - "bricktracker_sets"."rebrickable_set" + "bricktracker_sets"."set" {% endblock %} diff --git a/bricktracker/sql/set/select/full.sql b/bricktracker/sql/set/select/full.sql index a89d2c9..80d1161 100644 --- a/bricktracker/sql/set/select/full.sql +++ b/bricktracker/sql/set/select/full.sql @@ -5,7 +5,7 @@ WHERE "missing"."u_id" IS NOT DISTINCT FROM :id {% endblock %} {% block where_minifigures %} -WHERE "bricktracker_minifigures"."bricktracker_set_id" IS NOT DISTINCT FROM :id +WHERE "bricktracker_minifigures"."id" IS NOT DISTINCT FROM :id {% endblock %} {% block where %} diff --git a/bricktracker/sql/set/update/status.sql b/bricktracker/sql/set/update/status.sql index d72616e..4fc78e4 100644 --- a/bricktracker/sql/set/update/status.sql +++ b/bricktracker/sql/set/update/status.sql @@ -1,10 +1,10 @@ INSERT INTO "bricktracker_set_statuses" ( - "bricktracker_set_id", + "id", "{{name}}" ) VALUES ( :id, :status ) -ON CONFLICT("bricktracker_set_id") +ON CONFLICT("id") DO UPDATE SET "{{name}}" = :status -WHERE "bricktracker_set_statuses"."bricktracker_set_id" IS NOT DISTINCT FROM :id +WHERE "bricktracker_set_statuses"."id" IS NOT DISTINCT FROM :id -- 2.45.2 From f12d60873800566c5d3341fc5646d2265c61f012 Mon Sep 17 00:00:00 2001 From: Gregoo Date: Mon, 27 Jan 2025 23:13:42 +0100 Subject: [PATCH 18/19] Properly use the _listener variables as expected, and allow Enter key to execute the action --- static/scripts/socket/instructions.js | 21 ++++++++------ static/scripts/socket/set.js | 42 +++++++++++++++++---------- 2 files changed, 38 insertions(+), 25 deletions(-) diff --git a/static/scripts/socket/instructions.js b/static/scripts/socket/instructions.js index 271c0c3..fb19224 100644 --- a/static/scripts/socket/instructions.js +++ b/static/scripts/socket/instructions.js @@ -11,15 +11,9 @@ class BrickInstructionsSocket extends BrickSocket { this.html_files = document.getElementById(`${id}-files`); if (this.html_button) { - this.download_listener = ((bricksocket) => (e) => { - if (!bricksocket.disabled && bricksocket.socket !== undefined && bricksocket.socket.connected) { - bricksocket.toggle(false); - - bricksocket.download_instructions(); - } - })(this); - - this.html_button.addEventListener("click", this.download_listener); + this.download_listener = this.html_button.addEventListener("click", ((bricksocket) => (e) => { + bricksocket.execute(); + })(this)); } if (this.html_card_dismiss && this.html_card) { @@ -43,6 +37,15 @@ class BrickInstructionsSocket extends BrickSocket { this.download_instructions(true); } + // Execute the action + execute() { + if (!this.disabled && this.socket !== undefined && this.socket.connected) { + this.toggle(false); + + this.download_instructions(); + } + } + // Get the list of checkboxes describing files get_files(checked=false) { let files = []; diff --git a/static/scripts/socket/set.js b/static/scripts/socket/set.js index 60a2244..41056b8 100644 --- a/static/scripts/socket/set.js +++ b/static/scripts/socket/set.js @@ -5,6 +5,7 @@ class BrickSetSocket extends BrickSocket { // Listeners this.add_listener = undefined; + this.input_listener = undefined; this.confirm_listener = undefined; // Form elements (built based on the initial id) @@ -23,24 +24,15 @@ class BrickSetSocket extends BrickSocket { this.html_card_dismiss = document.getElementById(`${id}-card-dismiss`); if (this.html_button) { - this.add_listener = ((bricksocket) => (e) => { - if (!bricksocket.disabled && bricksocket.socket !== undefined && bricksocket.socket.connected) { - bricksocket.toggle(false); + this.add_listener = this.html_button.addEventListener("click", ((bricksocket) => (e) => { + bricksocket.execute(); + })(this)); - // Split and save the list if bulk - if (bricksocket.bulk) { - bricksocket.read_set_list() - } - - if (bricksocket.bulk || (bricksocket.html_no_confim && bricksocket.html_no_confim.checked)) { - bricksocket.import_set(true); - } else { - bricksocket.load_set(); - } + this.input_listener = this.html_input.addEventListener("keyup", ((bricksocket) => (e) => { + if (e.key === 'Enter') { + bricksocket.execute(); } - })(this); - - this.html_button.addEventListener("click", this.add_listener); + })(this)) } if (this.html_card_dismiss && this.html_card) { @@ -80,6 +72,24 @@ class BrickSetSocket extends BrickSocket { } } + // Execute the action + execute() { + if (!this.disabled && this.socket !== undefined && this.socket.connected) { + this.toggle(false); + + // Split and save the list if bulk + if (this.bulk) { + this.read_set_list(); + } + + if (this.bulk || (this.html_no_confim && this.html_no_confim.checked)) { + this.import_set(true); + } else { + this.load_set(); + } + } + } + // Upon receiving a fail message fail(data) { super.fail(data); -- 2.45.2 From 5615ebf40c6c2da4acc53fffb27aa08882c22877 Mon Sep 17 00:00:00 2001 From: Gregoo Date: Mon, 27 Jan 2025 23:24:16 +0100 Subject: [PATCH 19/19] Update versions and changelog --- CHANGELOG.md | 27 +++++++++++++++++++++++++++ bricktracker/version.py | 4 ++-- 2 files changed, 29 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index b87c902..8bdc3b8 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,32 @@ # Changelog +## Unreleased + +## Code + +- General cleanup + +- Minifigure + - Deduplicate + +- Socket + - Add decorator for rebrickable, authenticated and threaded socket actions + +- SQL + - Allow for advanced migration scenarios through companion python files + +### UI + +- Add + - Allow adding or bulk adding by pressing Enter in the input field + +- Admin + - Grey out legacy tables in the database view + +- Sets + - Add a flag to hide instructions in a set + + ## 1.1.1: PDF Instructions Download ### Instructions diff --git a/bricktracker/version.py b/bricktracker/version.py index b055c6b..4424778 100644 --- a/bricktracker/version.py +++ b/bricktracker/version.py @@ -1,4 +1,4 @@ from typing import Final -__version__: Final[str] = '1.1.1' -__database_version__: Final[int] = 6 +__version__: Final[str] = '1.2.0' +__database_version__: Final[int] = 9 -- 2.45.2