From 84f1b24b5a531c5099bcdd2a9e4e222d93599674 Mon Sep 17 00:00:00 2001 From: Gregoo Date: Mon, 27 Jan 2025 18:39:35 +0100 Subject: [PATCH] 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/version.py | 2 +- bricktracker/views/set.py | 2 +- templates/minifigure/card.html | 8 +- templates/minifigure/table.html | 2 +- templates/set/card.html | 2 +- 41 files changed, 442 insertions(+), 344 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/version.py b/bricktracker/version.py index 73c525d..4bcdeee 100644 --- a/bricktracker/version.py +++ b/bricktracker/version.py @@ -1,4 +1,4 @@ from typing import Final __version__: Final[str] = '1.1.0' -__database_version__: Final[int] = 6 +__database_version__: Final[int] = 8 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() %}