WIP: Initial work on deduplicating the minifigures and parts #57

Draft
gregoo wants to merge 19 commits from gregoo/BrickTracker:master into master
40 changed files with 441 additions and 343 deletions
Showing only changes of commit 1519f33e98 - Show all commits

View File

@ -121,10 +121,11 @@
# Optional: Change the default order of minifigures. By default ordered by insertion order. # Optional: Change the default order of minifigures. By default ordered by insertion order.
# Useful column names for this option are: # Useful column names for this option are:
# - "minifigures"."fig_num": minifigure ID (fig-xxxxx) # - "rebrickable_minifigures"."figure": minifigure ID (fig-xxxxx)
# - "minifigures"."name": minifigure name # - "rebrickable_minifigures"."number": minifigure ID as an integer (xxxxx)
# Default: "minifigures"."name" ASC # - "rebrickable_minifigures"."name": minifigure name
# BK_MINIFIGURES_DEFAULT_ORDER="minifigures"."name" ASC # 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 # Optional: Folder where to store the minifigures images, relative to the '/app/static/' folder
# Default: minifigs # Default: minifigs
@ -175,7 +176,7 @@
# BK_REBRICKABLE_IMAGE_NIL_MINIFIGURE= # BK_REBRICKABLE_IMAGE_NIL_MINIFIGURE=
# Optional: Pattern of the link to Rebrickable for a minifigure. Will be passed to Python .format() # 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= # BK_REBRICKABLE_LINK_MINIFIGURE_PATTERN=
# Optional: Pattern of the link to Rebrickable for a part. Will be passed to Python .format() # Optional: Pattern of the link to Rebrickable for a part. Will be passed to Python .format()

View File

@ -32,7 +32,7 @@ CONFIG: Final[list[dict[str, Any]]] = [
{'n': 'HIDE_MISSING_PARTS', 'c': bool}, {'n': 'HIDE_MISSING_PARTS', 'c': bool},
{'n': 'HIDE_SET_INSTRUCTIONS', 'c': bool}, {'n': 'HIDE_SET_INSTRUCTIONS', 'c': bool},
{'n': 'HIDE_WISHES', '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': 'MINIFIGURES_FOLDER', 'd': 'minifigs', 's': True},
{'n': 'NO_THREADED_SOCKET', 'c': bool}, {'n': 'NO_THREADED_SOCKET', 'c': bool},
{'n': 'PARTS_DEFAULT_ORDER', 'd': '"inventory"."name" ASC, "inventory"."color_name" ASC, "inventory"."is_spare" ASC'}, # noqa: E501 {'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_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', '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_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_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_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 {'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

View File

@ -1,48 +1,61 @@
from sqlite3 import Row import logging
from typing import Any, Self, TYPE_CHECKING import traceback
from typing import Self, TYPE_CHECKING
from flask import current_app, url_for
from .exceptions import ErrorException, NotFoundException from .exceptions import ErrorException, NotFoundException
from .part_list import BrickPartList from .part_list import BrickPartList
from .rebrickable_image import RebrickableImage from .rebrickable_parts import RebrickableParts
from .record import BrickRecord from .rebrickable_minifigure import RebrickableMinifigure
if TYPE_CHECKING: if TYPE_CHECKING:
from .set import BrickSet from .set import BrickSet
from .socket import BrickSocket
logger = logging.getLogger(__name__)
# Lego minifigure # Lego minifigure
class BrickMinifigure(BrickRecord): class BrickMinifigure(RebrickableMinifigure):
brickset: 'BrickSet | None'
# Queries # Queries
insert_query: str = 'minifigure/insert' insert_query: str = 'minifigure/insert'
generic_query: str = 'minifigure/select/generic' generic_query: str = 'minifigure/select/generic'
select_query: str = 'minifigure/select/specific' select_query: str = 'minifigure/select/specific'
def __init__( def download(self, socket: 'BrickSocket'):
self, if self.brickset is None:
/, raise ErrorException('Importing a minifigure from Rebrickable outside of a set is not supported') # noqa: E501
*,
brickset: 'BrickSet | None' = None,
record: Row | dict[str, Any] | None = None,
):
super().__init__()
# Save the brickset try:
self.brickset = brickset # 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 # Insert into database
if record is not None: self.insert(commit=False)
self.ingest(record)
# Return the number just in digits format # Insert the rebrickable set into database
def clean_number(self, /) -> str: self.insert_rebrickable()
number: str = self.fields.fig_num
number = number.removeprefix('fig-')
number = number.lstrip('0')
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 # Parts
def generic_parts(self, /) -> BrickPartList: def generic_parts(self, /) -> BrickPartList:
@ -51,108 +64,38 @@ class BrickMinifigure(BrickRecord):
# Parts # Parts
def parts(self, /) -> BrickPartList: def parts(self, /) -> BrickPartList:
if self.brickset is None: if self.brickset is None:
raise ErrorException('Part list for minifigure {number} requires a brickset'.format( # noqa: E501 raise ErrorException('Part list for minifigure {figure} requires a brickset'.format( # noqa: E501
number=self.fields.fig_num, figure=self.fields.figure,
)) ))
return BrickPartList().load(self.brickset, minifigure=self) return BrickPartList().load(self.brickset, minifigure=self)
# Select a generic minifigure # 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 # 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): if not self.select(override_query=self.generic_query):
raise NotFoundException( raise NotFoundException(
'Minifigure with number {number} was not found in the database'.format( # noqa: E501 'Minifigure with figure {figure} was not found in the database'.format( # noqa: E501
number=self.fields.fig_num, figure=self.fields.figure,
), ),
) )
return self return self
# Select a specific minifigure (with a set and an number) # Select a specific minifigure (with a set and a figure)
def select_specific(self, brickset: 'BrickSet', fig_num: str, /) -> Self: def select_specific(self, brickset: 'BrickSet', figure: str, /) -> Self:
# Save the parameters to the fields # Save the parameters to the fields
self.brickset = brickset self.brickset = brickset
self.fields.fig_num = fig_num self.fields.figure = figure
if not self.select(): if not self.select():
raise NotFoundException( raise NotFoundException(
'Minifigure with number {number} from set {set} was not found in the database'.format( # noqa: E501 'Minifigure with figure {figure} from set {set} was not found in the database'.format( # noqa: E501
number=self.fields.fig_num, figure=self.fields.figure,
set=self.brickset.fields.set, set=self.brickset.fields.set,
), ),
) )
return self 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

View File

@ -1,11 +1,17 @@
import logging
import traceback
from typing import Any, Self, TYPE_CHECKING from typing import Any, Self, TYPE_CHECKING
from flask import current_app from flask import current_app
from .minifigure import BrickMinifigure from .minifigure import BrickMinifigure
from .rebrickable import Rebrickable
from .record_list import BrickRecordList from .record_list import BrickRecordList
if TYPE_CHECKING: if TYPE_CHECKING:
from .set import BrickSet from .set import BrickSet
from .socket import BrickSocket
logger = logging.getLogger(__name__)
# Lego minifigures # Lego minifigures
@ -47,7 +53,7 @@ class BrickMinifigureList(BrickRecordList[BrickMinifigure]):
if current_app.config['RANDOM']: if current_app.config['RANDOM']:
order = 'RANDOM()' order = 'RANDOM()'
else: else:
order = 'minifigures.rowid DESC' order = '"bricktracker_minifigures"."rowid" DESC'
for record in self.select( for record in self.select(
override_query=self.last_query, override_query=self.last_query,
@ -73,16 +79,6 @@ class BrickMinifigureList(BrickRecordList[BrickMinifigure]):
return self 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 # Minifigures missing a part
def missing_part( def missing_part(
self, self,
@ -132,3 +128,51 @@ class BrickMinifigureList(BrickRecordList[BrickMinifigure]):
self.records.append(minifigure) self.records.append(minifigure)
return self 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())

View File

@ -137,7 +137,7 @@ class BrickPart(BrickRecord):
if 'set_num' not in parameters: if 'set_num' not in parameters:
if self.minifigure is not None: 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: elif self.brickset is not None:
parameters['set_num'] = self.brickset.fields.set parameters['set_num'] = self.brickset.fields.set
@ -215,14 +215,14 @@ class BrickPart(BrickRecord):
return url_for( return url_for(
'set.missing_minifigure_part', 'set.missing_minifigure_part',
id=self.fields.u_id, id=self.fields.u_id,
minifigure_id=self.minifigure.fields.fig_num, figure=self.minifigure.fields.figure,
part_id=self.fields.id, part=self.fields.id,
) )
return url_for( return url_for(
'set.missing_part', 'set.missing_part',
id=self.fields.u_id, id=self.fields.u_id,
part_id=self.fields.id part=self.fields.id
) )
# Compute the url for the rebrickable page # Compute the url for the rebrickable page

View File

@ -120,7 +120,7 @@ class BrickPartList(BrickRecordList[BrickPart]):
# Use the minifigure number if present, # Use the minifigure number if present,
# otherwise use the set number # otherwise use the set number
if self.minifigure is not None: 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: elif self.brickset is not None:
parameters['set_num'] = self.brickset.fields.set parameters['set_num'] = self.brickset.fields.set

View File

@ -8,7 +8,7 @@ from shutil import copyfileobj
from .exceptions import DownloadException from .exceptions import DownloadException
if TYPE_CHECKING: if TYPE_CHECKING:
from .minifigure import BrickMinifigure from .rebrickable_minifigure import RebrickableMinifigure
from .part import BrickPart from .part import BrickPart
from .rebrickable_set import RebrickableSet from .rebrickable_set import RebrickableSet
@ -16,7 +16,7 @@ if TYPE_CHECKING:
# A set, part or minifigure image from Rebrickable # A set, part or minifigure image from Rebrickable
class RebrickableImage(object): class RebrickableImage(object):
set: 'RebrickableSet' set: 'RebrickableSet'
minifigure: 'BrickMinifigure | None' minifigure: 'RebrickableMinifigure | None'
part: 'BrickPart | None' part: 'BrickPart | None'
extension: str | None extension: str | None
@ -26,7 +26,7 @@ class RebrickableImage(object):
set: 'RebrickableSet', set: 'RebrickableSet',
/, /,
*, *,
minifigure: 'BrickMinifigure | None' = None, minifigure: 'RebrickableMinifigure | None' = None,
part: 'BrickPart | None' = None, part: 'BrickPart | None' = None,
): ):
# Save all objects # Save all objects
@ -87,10 +87,10 @@ class RebrickableImage(object):
return self.part.fields.part_img_url_id return self.part.fields.part_img_url_id
if self.minifigure is not None: 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() return RebrickableImage.nil_minifigure_name()
else: else:
return self.minifigure.fields.fig_num return self.minifigure.fields.figure
return self.set.fields.set return self.set.fields.set
@ -111,10 +111,10 @@ class RebrickableImage(object):
return self.part.fields.part_img_url return self.part.fields.part_img_url
if self.minifigure is not None: 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'] return current_app.config['REBRICKABLE_IMAGE_NIL_MINIFIGURE']
else: else:
return self.minifigure.fields.set_img_url return self.minifigure.fields.image
return self.set.fields.image return self.set.fields.image

View File

@ -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'],
}

View File

@ -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()

View File

@ -40,7 +40,7 @@ class RebrickableParts(object):
self.minifigure = minifigure self.minifigure = minifigure
if self.minifigure is not None: if self.minifigure is not None:
self.number = self.minifigure.fields.fig_num self.number = self.minifigure.fields.figure
self.kind = 'Minifigure' self.kind = 'Minifigure'
self.method = 'get_minifig_elements' self.method = 'get_minifig_elements'
else: else:

View File

@ -8,7 +8,6 @@ from flask import current_app, url_for
from .exceptions import DatabaseException, NotFoundException from .exceptions import DatabaseException, NotFoundException
from .minifigure_list import BrickMinifigureList from .minifigure_list import BrickMinifigureList
from .part_list import BrickPartList from .part_list import BrickPartList
from .rebrickable_minifigures import RebrickableMinifigures
from .rebrickable_parts import RebrickableParts from .rebrickable_parts import RebrickableParts
from .rebrickable_set import RebrickableSet from .rebrickable_set import RebrickableSet
from .set_checkbox import BrickSetCheckbox from .set_checkbox import BrickSetCheckbox
@ -55,14 +54,14 @@ class BrickSet(RebrickableSet):
# Insert into database # Insert into database
self.insert(commit=False) self.insert(commit=False)
# Execute the parent download method # Insert the rebrickable set into database
self.insert_rebrickable() self.insert_rebrickable()
# Load the inventory # Load the inventory
RebrickableParts(socket, self).download() RebrickableParts(socket, self).download()
# Load the minifigures # Load the minifigures
RebrickableMinifigureList(socket, self).download() BrickMinifigureList.download(socket, self)
# Commit the transaction to the database # Commit the transaction to the database
socket.auto_progress( socket.auto_progress(

View File

@ -82,13 +82,9 @@ class BrickSetList(BrickRecordList[BrickSet]):
return self return self
# Sets missing a minifigure # Sets missing a minifigure
def missing_minifigure( def missing_minifigure(self, figure: str, /) -> Self:
self,
fig_num: str,
/
) -> Self:
# Save the parameters to the fields # Save the parameters to the fields
self.fields.fig_num = fig_num self.fields.figure = figure
# Load the sets from the database # Load the sets from the database
for record in self.select( for record in self.select(
@ -127,13 +123,9 @@ class BrickSetList(BrickRecordList[BrickSet]):
return self return self
# Sets using a minifigure # Sets using a minifigure
def using_minifigure( def using_minifigure(self, figure: str, /) -> Self:
self,
fig_num: str,
/
) -> Self:
# Save the parameters to the fields # Save the parameters to the fields
self.fields.fig_num = fig_num self.fields.figure = figure
# Load the sets from the database # Load the sets from the database
for record in self.select( for record in self.select(

View File

@ -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;

View File

@ -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;

View File

@ -1,10 +1,10 @@
SELECT SELECT
"minifigures"."fig_num", {% block set %}{% endblock %}
"minifigures"."set_num", "bricktracker_minifigures"."quantity",
"minifigures"."name", "rebrickable_minifigures"."figure",
"minifigures"."quantity", "rebrickable_minifigures"."number",
"minifigures"."set_img_url", "rebrickable_minifigures"."name",
"minifigures"."u_id", "rebrickable_minifigures"."image",
{% block total_missing %} {% block total_missing %}
NULL AS "total_missing", -- dummy for order: total_missing NULL AS "total_missing", -- dummy for order: total_missing
{% endblock %} {% endblock %}
@ -14,7 +14,10 @@ SELECT
{% block total_sets %} {% block total_sets %}
NULL AS "total_sets" -- dummy for order: total_sets NULL AS "total_sets" -- dummy for order: total_sets
{% endblock %} {% 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 %} {% block join %}{% endblock %}

View File

@ -1,15 +1,9 @@
INSERT INTO "minifigures" ( INSERT INTO "bricktracker_minifigures" (
"fig_num", "bricktracker_set_id",
"set_num", "rebrickable_figure",
"name", "quantity"
"quantity",
"set_img_url",
"u_id"
) VALUES ( ) VALUES (
:fig_num, :bricktracker_set_id,
:set_num, :figure,
:name, :quantity
:quantity,
:set_img_url,
:u_id
) )

View File

@ -1,15 +1,15 @@
{% extends 'minifigure/base/select.sql' %} {% extends 'minifigure/base/base.sql' %}
{% block total_missing %} {% block total_missing %}
SUM(IFNULL("missing_join"."total", 0)) AS "total_missing", SUM(IFNULL("missing_join"."total", 0)) AS "total_missing",
{% endblock %} {% endblock %}
{% block total_quantity %} {% block total_quantity %}
SUM(IFNULL("minifigures"."quantity", 0)) AS "total_quantity", SUM(IFNULL("bricktracker_minifigures"."quantity", 0)) AS "total_quantity",
{% endblock %} {% endblock %}
{% block total_sets %} {% block total_sets %}
COUNT("minifigures"."set_num") AS "total_sets" COUNT("bricktracker_minifigures"."bricktracker_set_id") AS "total_sets"
{% endblock %} {% endblock %}
{% block join %} {% block join %}
@ -24,11 +24,11 @@ LEFT JOIN (
"missing"."set_num", "missing"."set_num",
"missing"."u_id" "missing"."u_id"
) missing_join ) missing_join
ON "minifigures"."u_id" IS NOT DISTINCT FROM "missing_join"."u_id" ON "bricktracker_minifigures"."bricktracker_set_id" IS NOT DISTINCT FROM "missing_join"."u_id"
AND "minifigures"."fig_num" IS NOT DISTINCT FROM "missing_join"."set_num" AND "rebrickable_minifigures"."figure" IS NOT DISTINCT FROM "missing_join"."set_num"
{% endblock %} {% endblock %}
{% block group %} {% block group %}
GROUP BY GROUP BY
"minifigures"."fig_num" "rebrickable_minifigures"."figure"
{% endblock %} {% endblock %}

View File

@ -1,6 +1,5 @@
{% extends 'minifigure/base/select.sql' %} {% extends 'minifigure/base/base.sql' %}
{% block where %} {% block where %}
WHERE "minifigures"."u_id" IS NOT DISTINCT FROM :u_id WHERE "bricktracker_minifigures"."bricktracker_set_id" IS NOT DISTINCT FROM :bricktracker_set_id
AND "minifigures"."set_num" IS NOT DISTINCT FROM :set_num
{% endblock %} {% endblock %}

View File

@ -1,4 +1,4 @@
{% extends 'minifigure/base/select.sql' %} {% extends 'minifigure/base/base.sql' %}
{% block total_missing %} {% block total_missing %}
SUM(IFNULL("missing"."quantity", 0)) AS "total_missing", SUM(IFNULL("missing"."quantity", 0)) AS "total_missing",
@ -6,12 +6,12 @@ SUM(IFNULL("missing"."quantity", 0)) AS "total_missing",
{% block join %} {% block join %}
LEFT JOIN "missing" LEFT JOIN "missing"
ON "minifigures"."fig_num" IS NOT DISTINCT FROM "missing"."set_num" ON "rebrickable_minifigures"."figure" IS NOT DISTINCT FROM "missing"."set_num"
AND "minifigures"."u_id" IS NOT DISTINCT FROM "missing"."u_id" AND "bricktracker_minifigures"."bricktracker_set_id" IS NOT DISTINCT FROM "missing"."u_id"
{% endblock %} {% endblock %}
{% block group %} {% block group %}
GROUP BY GROUP BY
"minifigures"."fig_num", "rebrickable_minifigures"."figure",
"minifigures"."u_id" "bricktracker_minifigures"."bricktracker_set_id"
{% endblock %} {% endblock %}

View File

@ -1,4 +1,4 @@
{% extends 'minifigure/base/select.sql' %} {% extends 'minifigure/base/base.sql' %}
{% block total_missing %} {% block total_missing %}
SUM(IFNULL("missing"."quantity", 0)) AS "total_missing", SUM(IFNULL("missing"."quantity", 0)) AS "total_missing",
@ -6,12 +6,12 @@ SUM(IFNULL("missing"."quantity", 0)) AS "total_missing",
{% block join %} {% block join %}
LEFT JOIN "missing" LEFT JOIN "missing"
ON "minifigures"."fig_num" IS NOT DISTINCT FROM "missing"."set_num" ON "rebrickable_minifigures"."figure" IS NOT DISTINCT FROM "missing"."set_num"
AND "minifigures"."u_id" IS NOT DISTINCT FROM "missing"."u_id" AND "bricktracker_minifigures"."bricktracker_set_id" IS NOT DISTINCT FROM "missing"."u_id"
{% endblock %} {% endblock %}
{% block where %} {% block where %}
WHERE "minifigures"."fig_num" IN ( WHERE "rebrickable_minifigures"."figure" IN (
SELECT SELECT
"missing"."set_num" "missing"."set_num"
FROM "missing" FROM "missing"
@ -26,5 +26,5 @@ WHERE "minifigures"."fig_num" IN (
{% block group %} {% block group %}
GROUP BY GROUP BY
"minifigures"."fig_num" "rebrickable_minifigures"."figure"
{% endblock %} {% endblock %}

View File

@ -1,11 +1,11 @@
{% extends 'minifigure/base/select.sql' %} {% extends 'minifigure/base/base.sql' %}
{% block total_quantity %} {% block total_quantity %}
SUM("minifigures"."quantity") AS "total_quantity", SUM("bricktracker_minifigures"."quantity") AS "total_quantity",
{% endblock %} {% endblock %}
{% block where %} {% block where %}
WHERE "minifigures"."fig_num" IN ( WHERE "rebrickable_minifigures"."figure" IN (
SELECT SELECT
"inventory"."set_num" "inventory"."set_num"
FROM "inventory" FROM "inventory"
@ -20,5 +20,5 @@ WHERE "minifigures"."fig_num" IN (
{% block group %} {% block group %}
GROUP BY GROUP BY
"minifigures"."fig_num" "rebrickable_minifigures"."figure"
{% endblock %} {% endblock %}

View File

@ -1,38 +1,28 @@
{% extends 'minifigure/base/select.sql' %} {% extends 'minifigure/base/base.sql' %}
{% block total_missing %} {% block total_missing %}
SUM(IFNULL("missing_join"."total", 0)) AS "total_missing", SUM(IFNULL("missing"."quantity", 0)) AS "total_missing",
{% endblock %} {% endblock %}
{% block total_quantity %} {% block total_quantity %}
SUM(IFNULL("minifigures"."quantity", 0)) AS "total_quantity", SUM(IFNULL("bricktracker_minifigures"."quantity", 0)) AS "total_quantity",
{% endblock %} {% endblock %}
{% block total_sets %} {% block total_sets %}
COUNT("minifigures"."set_num") AS "total_sets" COUNT(DISTINCT "bricktracker_minifigures"."bricktracker_set_id") AS "total_sets"
{% endblock %} {% endblock %}
{% block join %} {% block join %}
-- LEFT JOIN + SELECT to avoid messing the total LEFT JOIN "missing"
LEFT JOIN ( ON "rebrickable_minifigures"."figure" IS NOT DISTINCT FROM "missing"."set_num"
SELECT AND "bricktracker_minifigures"."bricktracker_set_id" IS NOT DISTINCT FROM "missing"."u_id"
"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"
{% endblock %} {% endblock %}
{% block where %} {% block where %}
WHERE "minifigures"."fig_num" IS NOT DISTINCT FROM :fig_num WHERE "rebrickable_minifigures"."figure" IS NOT DISTINCT FROM :figure
{% endblock %} {% endblock %}
{% block group %} {% block group %}
GROUP BY GROUP BY
"minifigures"."fig_num" "rebrickable_minifigures"."figure"
{% endblock %} {% endblock %}

View File

@ -1,7 +1,6 @@
{% extends 'minifigure/base/select.sql' %} {% extends 'minifigure/base/base.sql' %}
{% block where %} {% block where %}
WHERE "minifigures"."fig_num" IS NOT DISTINCT FROM :fig_num WHERE "rebrickable_minifigures"."figure" IS NOT DISTINCT FROM :figure
AND "minifigures"."u_id" IS NOT DISTINCT FROM :u_id AND "bricktracker_minifigures"."bricktracker_set_id" IS NOT DISTINCT FROM :bricktracker_set_id
AND "minifigures"."set_num" IS NOT DISTINCT FROM :set_num
{% endblock %} {% endblock %}

View File

@ -5,15 +5,15 @@ SUM(IFNULL("missing"."quantity", 0)) AS "total_missing",
{% endblock %} {% endblock %}
{% block total_quantity %} {% 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 %} {% endblock %}
{% block total_sets %} {% block total_sets %}
COUNT(DISTINCT "bricktracker_sets"."id") AS "total_sets", COUNT(DISTINCT "bricktracker_minifigures"."bricktracker_set_id") AS "total_sets",
{% endblock %} {% endblock %}
{% block total_minifigures %} {% block total_minifigures %}
SUM(IFNULL("minifigures"."quantity", 0)) AS "total_minifigures" SUM(IFNULL("bricktracker_minifigures"."quantity", 0)) AS "total_minifigures"
{% endblock %} {% endblock %}
{% block join %} {% 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"."element_id" IS NOT DISTINCT FROM "missing"."element_id"
AND "inventory"."u_id" IS NOT DISTINCT FROM "missing"."u_id" AND "inventory"."u_id" IS NOT DISTINCT FROM "missing"."u_id"
LEFT JOIN "minifigures" LEFT JOIN "bricktracker_minifigures"
ON "inventory"."set_num" IS NOT DISTINCT FROM "minifigures"."fig_num" ON "inventory"."set_num" IS NOT DISTINCT FROM "bricktracker_minifigures"."rebrickable_figure"
AND "inventory"."u_id" IS NOT DISTINCT FROM "minifigures"."u_id" AND "inventory"."u_id" IS NOT DISTINCT FROM "bricktracker_minifigures"."bricktracker_set_id"
LEFT JOIN "bricktracker_sets"
ON "inventory"."u_id" IS NOT DISTINCT FROM "bricktracker_sets"."id"
{% endblock %} {% endblock %}
{% block group %} {% block group %}

View File

@ -5,11 +5,11 @@ SUM(IFNULL("missing"."quantity", 0)) AS "total_missing",
{% endblock %} {% endblock %}
{% block total_sets %} {% 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 %} {% endblock %}
{% block total_minifigures %} {% block total_minifigures %}
SUM(IFNULL("minifigures"."quantity", 0)) AS "total_minifigures" SUM(IFNULL("bricktracker_minifigures"."quantity", 0)) AS "total_minifigures"
{% endblock %} {% endblock %}
{% block join %} {% 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"."element_id" IS NOT DISTINCT FROM "inventory"."element_id"
AND "missing"."u_id" IS NOT DISTINCT FROM "inventory"."u_id" AND "missing"."u_id" IS NOT DISTINCT FROM "inventory"."u_id"
LEFT JOIN "minifigures" LEFT JOIN "bricktracker_minifigures"
ON "missing"."set_num" IS NOT DISTINCT FROM "minifigures"."fig_num" ON "inventory"."set_num" IS NOT DISTINCT FROM "bricktracker_minifigures"."rebrickable_figure"
AND "missing"."u_id" IS NOT DISTINCT FROM "minifigures"."u_id" AND "inventory"."u_id" IS NOT DISTINCT FROM "bricktracker_minifigures"."bricktracker_set_id"
{% endblock %} {% endblock %}
{% block group %} {% block group %}

View File

@ -5,11 +5,11 @@ SUM(IFNULL("missing"."quantity", 0)) AS "total_missing",
{% endblock %} {% endblock %}
{% block total_quantity %} {% 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 %} {% endblock %}
{% block total_spare %} {% 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 %} {% endblock %}
{% block join %} {% 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"."element_id" IS NOT DISTINCT FROM "missing"."element_id"
AND "inventory"."u_id" IS NOT DISTINCT FROM "missing"."u_id" AND "inventory"."u_id" IS NOT DISTINCT FROM "missing"."u_id"
LEFT JOIN "minifigures" LEFT JOIN "bricktracker_minifigures"
ON "inventory"."set_num" IS NOT DISTINCT FROM "minifigures"."fig_num" ON "inventory"."set_num" IS NOT DISTINCT FROM "bricktracker_minifigures"."rebrickable_figure"
AND "inventory"."u_id" IS NOT DISTINCT FROM "minifigures"."u_id" AND "inventory"."u_id" IS NOT DISTINCT FROM "bricktracker_minifigures"."bricktracker_set_id"
{% endblock %} {% endblock %}
{% block where %} {% block where %}

View File

@ -0,0 +1,11 @@
INSERT OR IGNORE INTO "rebrickable_minifigures" (
"figure",
"number",
"name",
"image"
) VALUES (
:figure,
:number,
:name,
:image
)

View File

@ -0,0 +1,6 @@
SELECT
"rebrickable_minifigures"."figure",
"rebrickable_minifigures"."number",
"rebrickable_minifigures"."name",
"rebrickable_minifigures"."image"
FROM "rebrickable_minifigures"

View File

@ -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

View File

@ -1,12 +1,15 @@
BEGIN transaction; BEGIN transaction;
DROP TABLE IF EXISTS "bricktracker_minifigures";
DROP TABLE IF EXISTS "bricktracker_sets"; DROP TABLE IF EXISTS "bricktracker_sets";
DROP TABLE IF EXISTS "bricktracker_set_checkboxes"; DROP TABLE IF EXISTS "bricktracker_set_checkboxes";
DROP TABLE IF EXISTS "bricktracker_set_statuses"; DROP TABLE IF EXISTS "bricktracker_set_statuses";
DROP TABLE IF EXISTS "bricktracker_wishes"; DROP TABLE IF EXISTS "bricktracker_wishes";
DROP TABLE IF EXISTS "inventory"; DROP TABLE IF EXISTS "inventory";
DROP TABLE IF EXISTS "minifigures"; DROP TABLE IF EXISTS "minifigures";
DROP TABLE IF EXISTS "minifigures_old";
DROP TABLE IF EXISTS "missing"; DROP TABLE IF EXISTS "missing";
DROP TABLE IF EXISTS "rebrickable_minifigures";
DROP TABLE IF EXISTS "rebrickable_sets"; DROP TABLE IF EXISTS "rebrickable_sets";
DROP TABLE IF EXISTS "sets"; DROP TABLE IF EXISTS "sets";
DROP TABLE IF EXISTS "sets_old"; DROP TABLE IF EXISTS "sets_old";

View File

@ -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 to avoid messing the total
LEFT JOIN ( LEFT JOIN (
SELECT SELECT
"minifigures"."u_id", "bricktracker_minifigures"."bricktracker_set_id",
SUM("minifigures"."quantity") AS "total" SUM("bricktracker_minifigures"."quantity") AS "total"
FROM "minifigures" FROM "bricktracker_minifigures"
{% block where_minifigures %}{% endblock %} {% block where_minifigures %}{% endblock %}
GROUP BY "u_id" GROUP BY "bricktracker_minifigures"."bricktracker_set_id"
) "minifigures_join" ) "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 %} {% endblock %}

View File

@ -9,8 +9,8 @@ WHERE "bricktracker_sets"."id" IS NOT DISTINCT FROM '{{ id }}';
DELETE FROM "bricktracker_set_statuses" DELETE FROM "bricktracker_set_statuses"
WHERE "bricktracker_set_statuses"."bricktracker_set_id" IS NOT DISTINCT FROM '{{ id }}'; WHERE "bricktracker_set_statuses"."bricktracker_set_id" IS NOT DISTINCT FROM '{{ id }}';
DELETE FROM "minifigures" DELETE FROM "bricktracker_minifigures"
WHERE "minifigures"."u_id" IS NOT DISTINCT FROM '{{ id }}'; WHERE "bricktracker_minifigures"."bricktracker_set_id" IS NOT DISTINCT FROM '{{ id }}';
DELETE FROM "missing" DELETE FROM "missing"
WHERE "missing"."u_id" IS NOT DISTINCT FROM '{{ id }}'; WHERE "missing"."u_id" IS NOT DISTINCT FROM '{{ id }}';

View File

@ -6,7 +6,7 @@ WHERE "bricktracker_sets"."id" IN (
"missing"."u_id" "missing"."u_id"
FROM "missing" 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" GROUP BY "missing"."u_id"
) )

View File

@ -6,7 +6,7 @@ WHERE "bricktracker_sets"."id" IN (
"inventory"."u_id" "inventory"."u_id"
FROM "inventory" 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" GROUP BY "inventory"."u_id"
) )

View File

@ -5,7 +5,7 @@ WHERE "missing"."u_id" IS NOT DISTINCT FROM :id
{% endblock %} {% endblock %}
{% block where_minifigures %} {% block where_minifigures %}
WHERE "minifigures"."u_id" IS NOT DISTINCT FROM :id WHERE "bricktracker_minifigures"."bricktracker_set_id" IS NOT DISTINCT FROM :id
{% endblock %} {% endblock %}
{% block where %} {% block where %}

View File

@ -2,13 +2,15 @@ from typing import Tuple
# Some table aliases to make it look cleaner (id: (name, icon)) # Some table aliases to make it look cleaner (id: (name, icon))
ALIASES: dict[str, Tuple[str, str]] = { 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_set_statuses': ('Bricktracker sets status', 'checkbox-line'),
'bricktracker_sets': ('Bricktracker sets', 'hashtag'), 'bricktracker_sets': ('Bricktracker sets', 'hashtag'),
'bricktracker_wishes': ('Bricktracker wishes', 'gift-line'), 'bricktracker_wishes': ('Bricktracker wishes', 'gift-line'),
'inventory': ('Parts', 'shapes-line'), 'inventory': ('Parts', 'shapes-line'),
'minifigures': ('Minifigures', 'group-line'), 'minifigures': ('Minifigures', 'group-line'),
'minifigures_old': ('Minifigures (legacy)', 'group-line'),
'missing': ('Missing', 'error-warning-line'), 'missing': ('Missing', 'error-warning-line'),
'rebrickable_minifigures': ('Rebrickable minifigures', 'group-line'),
'rebrickable_sets': ('Rebrickable sets', 'hashtag'), 'rebrickable_sets': ('Rebrickable sets', 'hashtag'),
'sets': ('Sets', 'hashtag'), 'sets': ('Sets', 'hashtag'),
'sets_old': ('Sets (legacy)', 'hashtag'), 'sets_old': ('Sets (legacy)', 'hashtag'),

View File

@ -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 logger.info('Set {number} ({id}): updated minifigure ({figure}) part ({part}) missing count to {missing}'.format( # noqa: E501
number=brickset.fields.set, number=brickset.fields.set,
id=brickset.fields.id, id=brickset.fields.id,
figure=brickminifigure.fields.fig_num, figure=brickminifigure.fields.figure,
part=brickpart.fields.id, part=brickpart.fields.id,
missing=missing, missing=missing,
)) ))

View File

@ -3,11 +3,11 @@
{% import 'macro/card.html' as card %} {% import 'macro/card.html' as card %}
<div class="card mb-3 flex-fill {% if solo %}card-solo{% endif %}"> <div class="card mb-3 flex-fill {% if solo %}card-solo{% endif %}">
{{ card.header(item, item.fields.name, solo=solo, number=item.clean_number(), icon='user-line') }} {{ 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.fig_num, medium=true) }} {{ card.image(item, solo=solo, last=last, caption=item.fields.name, alt=item.fields.figure, medium=true) }}
<div class="card-body border-bottom {% if not solo %}p-1{% endif %}"> <div class="card-body border-bottom {% if not solo %}p-1{% endif %}">
{% if last %} {% 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) }} {{ badge.quantity(item.fields.quantity, solo=solo, last=last) }}
{% endif %} {% endif %}
{{ badge.quantity(item.fields.total_quantity, solo=solo, last=last) }} {{ badge.quantity(item.fields.total_quantity, solo=solo, last=last) }}
@ -19,7 +19,7 @@
</div> </div>
{% if solo %} {% if solo %}
<div class="accordion accordion-flush" id="minifigure-details"> <div class="accordion accordion-flush" id="minifigure-details">
{{ 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(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') }} {{ accordion.cards(missing, 'Sets missing parts of this minifigure', 'missing-inventory', 'minifigure-details', 'set/card.html', icon='error-warning-line') }}
</div> </div>

View File

@ -6,7 +6,7 @@
<tbody> <tbody>
{% for item in table_collection %} {% for item in table_collection %}
<tr> <tr>
{{ 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) }}
<td > <td >
<a class="text-reset" href="{{ item.url() }}" style="max-width:auto">{{ item.fields.name }}</a> <a class="text-reset" href="{{ item.url() }}" style="max-width:auto">{{ item.fields.name }}</a>
{% if all %} {% if all %}

View File

@ -57,7 +57,7 @@
{{ accordion.footer() }} {{ accordion.footer() }}
{{ accordion.table(item.parts(), 'Parts', 'parts-inventory', 'set-details', 'part/table.html', icon='shapes-line')}} {{ accordion.table(item.parts(), 'Parts', 'parts-inventory', 'set-details', 'part/table.html', icon='shapes-line')}}
{% for minifigure in item.minifigures() %} {% 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 %} {% endfor %}
{% endif %} {% endif %}
{% if g.login.is_authenticated() %} {% if g.login.is_authenticated() %}