feat(views): update existing models to support individual items integration
This commit is contained in:
@@ -20,8 +20,8 @@ class BrickMinifigureList(BrickRecordList[BrickMinifigure]):
|
||||
order: str
|
||||
|
||||
# Queries
|
||||
all_query: str = 'minifigure/list/all'
|
||||
all_by_owner_query: str = 'minifigure/list/all_by_owner'
|
||||
all_query: str = 'minifigure/list/all_unified'
|
||||
all_by_owner_query: str = 'minifigure/list/all_by_owner_unified'
|
||||
damaged_part_query: str = 'minifigure/list/damaged_part'
|
||||
last_query: str = 'minifigure/list/last'
|
||||
missing_part_query: str = 'minifigure/list/missing_part'
|
||||
@@ -44,7 +44,7 @@ class BrickMinifigureList(BrickRecordList[BrickMinifigure]):
|
||||
return self
|
||||
|
||||
# Load all minifigures with problems filter
|
||||
def all_filtered(self, /, owner_id: str | None = None, problems_filter: str = 'all', theme_id: str = 'all', year: str = 'all') -> Self:
|
||||
def all_filtered(self, /, owner_id: str | None = None, problems_filter: str = 'all', theme_id: str = 'all', year: str = 'all', individuals_filter: str = 'all') -> Self:
|
||||
# Save the owner_id parameter
|
||||
if owner_id is not None:
|
||||
self.fields.owner_id = owner_id
|
||||
@@ -56,6 +56,8 @@ class BrickMinifigureList(BrickRecordList[BrickMinifigure]):
|
||||
context['theme_id'] = theme_id
|
||||
if year and year != 'all':
|
||||
context['year'] = year
|
||||
if individuals_filter and individuals_filter != 'all':
|
||||
context['individuals_filter'] = individuals_filter
|
||||
|
||||
# Choose query based on whether owner filtering is needed
|
||||
if owner_id and owner_id != 'all':
|
||||
@@ -77,7 +79,7 @@ class BrickMinifigureList(BrickRecordList[BrickMinifigure]):
|
||||
return self
|
||||
|
||||
# Load all minifigures by owner with problems filter
|
||||
def all_by_owner_filtered(self, /, owner_id: str | None = None, problems_filter: str = 'all', theme_id: str = 'all', year: str = 'all') -> Self:
|
||||
def all_by_owner_filtered(self, /, owner_id: str | None = None, problems_filter: str = 'all', theme_id: str = 'all', year: str = 'all', individuals_filter: str = 'all') -> Self:
|
||||
# Save the owner_id parameter
|
||||
self.fields.owner_id = owner_id
|
||||
|
||||
@@ -88,6 +90,8 @@ class BrickMinifigureList(BrickRecordList[BrickMinifigure]):
|
||||
context['theme_id'] = theme_id
|
||||
if year and year != 'all':
|
||||
context['year'] = year
|
||||
if individuals_filter and individuals_filter != 'all':
|
||||
context['individuals_filter'] = individuals_filter
|
||||
|
||||
# Load the minifigures from the database
|
||||
self.list(override_query=self.all_by_owner_query, **context)
|
||||
@@ -101,6 +105,7 @@ class BrickMinifigureList(BrickRecordList[BrickMinifigure]):
|
||||
problems_filter: str = 'all',
|
||||
theme_id: str = 'all',
|
||||
year: str = 'all',
|
||||
individuals_filter: str = 'all',
|
||||
search_query: str | None = None,
|
||||
page: int = 1,
|
||||
per_page: int = 50,
|
||||
@@ -127,10 +132,13 @@ class BrickMinifigureList(BrickRecordList[BrickMinifigure]):
|
||||
if year and year != 'all':
|
||||
filter_context['year'] = year
|
||||
|
||||
# Field mapping for sorting
|
||||
if individuals_filter and individuals_filter != 'all':
|
||||
filter_context['individuals_filter'] = individuals_filter
|
||||
|
||||
# Field mapping for sorting (using column names from the unified query)
|
||||
field_mapping = {
|
||||
'name': '"rebrickable_minifigures"."name"',
|
||||
'parts': '"rebrickable_minifigures"."number_of_parts"',
|
||||
'name': '"name"',
|
||||
'parts': '"number_of_parts"',
|
||||
'quantity': '"total_quantity"',
|
||||
'missing': '"total_missing"',
|
||||
'damaged': '"total_damaged"',
|
||||
|
||||
+28
-2
@@ -17,7 +17,7 @@ def parse_set(set: str, /) -> str:
|
||||
if version == '':
|
||||
version = '1'
|
||||
|
||||
# Version must be a positive integer
|
||||
# Version must be a valid number (but preserve leading zeros for minifigures)
|
||||
try:
|
||||
version_int = int(version)
|
||||
except Exception:
|
||||
@@ -30,4 +30,30 @@ def parse_set(set: str, /) -> str:
|
||||
version=version,
|
||||
))
|
||||
|
||||
return '{number}-{version}'.format(number=number, version=version_int)
|
||||
# Preserve original version string to keep leading zeros (important for minifigures like fig-000484)
|
||||
return '{number}-{version}'.format(number=number, version=version)
|
||||
|
||||
|
||||
# Make sense of string supposed to contain a minifigure ID
|
||||
def parse_minifig(figure: str, /) -> str:
|
||||
# Minifigure format is typically fig-XXXXXX
|
||||
# We'll accept with or without the 'fig-' prefix
|
||||
figure = figure.strip()
|
||||
|
||||
if not figure.startswith('fig-'):
|
||||
# Try to add the prefix if it's just numbers
|
||||
if figure.isdigit():
|
||||
figure = 'fig-{figure}'.format(figure=figure.zfill(6))
|
||||
else:
|
||||
raise ErrorException('Minifigure "{figure}" must start with "fig-"'.format(
|
||||
figure=figure,
|
||||
))
|
||||
|
||||
# Validate format: fig-XXXXXX where X can be digits or letters
|
||||
parts = figure.split('-')
|
||||
if len(parts) != 2 or parts[0] != 'fig':
|
||||
raise ErrorException('Invalid minifigure format "{figure}". Expected format: fig-XXXXXX'.format(
|
||||
figure=figure,
|
||||
))
|
||||
|
||||
return figure
|
||||
|
||||
+142
-27
@@ -9,6 +9,7 @@ from .exceptions import ErrorException, NotFoundException
|
||||
from .rebrickable_part import RebrickablePart
|
||||
from .sql import BrickSQL
|
||||
if TYPE_CHECKING:
|
||||
from .individual_minifigure import IndividualMinifigure
|
||||
from .minifigure import BrickMinifigure
|
||||
from .set import BrickSet
|
||||
from .socket import BrickSocket
|
||||
@@ -33,6 +34,7 @@ class BrickPart(RebrickablePart):
|
||||
*,
|
||||
brickset: 'BrickSet | None' = None,
|
||||
minifigure: 'BrickMinifigure | None' = None,
|
||||
individual_minifigure: 'IndividualMinifigure | None' = None,
|
||||
record: Row | dict[str, Any] | None = None
|
||||
):
|
||||
super().__init__(
|
||||
@@ -41,7 +43,12 @@ class BrickPart(RebrickablePart):
|
||||
record=record
|
||||
)
|
||||
|
||||
if self.minifigure is not None:
|
||||
self.individual_minifigure = individual_minifigure
|
||||
|
||||
if self.individual_minifigure is not None:
|
||||
self.identifier = self.individual_minifigure.fields.id
|
||||
self.kind = 'Individual Minifigure'
|
||||
elif self.minifigure is not None:
|
||||
self.identifier = self.minifigure.fields.figure
|
||||
self.kind = 'Minifigure'
|
||||
elif self.brickset is not None:
|
||||
@@ -182,6 +189,33 @@ class BrickPart(RebrickablePart):
|
||||
|
||||
return self
|
||||
|
||||
# Select a specific part from an individual minifigure instance
|
||||
def select_specific_individual_minifigure(
|
||||
self,
|
||||
individual_minifigure: 'IndividualMinifigure',
|
||||
part: str,
|
||||
color: int,
|
||||
spare: int,
|
||||
/,
|
||||
) -> Self:
|
||||
# Save the parameters to the fields
|
||||
self.individual_minifigure = individual_minifigure
|
||||
self.fields.part = part
|
||||
self.fields.color = color
|
||||
self.fields.spare = spare
|
||||
|
||||
if not self.select(override_query='individual_minifigure/part/select/specific'):
|
||||
raise NotFoundException(
|
||||
'Part {part} with color {color} (spare: {spare}) from individual minifigure {id} was not found in the database'.format( # noqa: E501
|
||||
part=self.fields.part,
|
||||
color=self.fields.color,
|
||||
spare=self.fields.spare,
|
||||
id=individual_minifigure.fields.id,
|
||||
),
|
||||
)
|
||||
|
||||
return self
|
||||
|
||||
# Update checked state for part walkthrough
|
||||
def update_checked(self, json: Any | None, /) -> bool:
|
||||
# Handle both direct 'checked' key and changer.js 'value' key format
|
||||
@@ -202,22 +236,56 @@ class BrickPart(RebrickablePart):
|
||||
|
||||
return checked
|
||||
|
||||
# Update checked state for individual minifigure part
|
||||
def update_checked_individual_minifigure(self, json: Any | None, /) -> bool:
|
||||
# Handle both direct 'checked' key and changer.js 'value' key format
|
||||
if json:
|
||||
checked = json.get('checked', json.get('value', False))
|
||||
else:
|
||||
checked = False
|
||||
|
||||
checked = bool(checked)
|
||||
|
||||
self.fields.checked = checked
|
||||
|
||||
BrickSQL().execute_and_commit(
|
||||
'individual_minifigure/part/update/checked',
|
||||
parameters=self.sql_parameters()
|
||||
)
|
||||
|
||||
return checked
|
||||
|
||||
# Compute the url for updating checked state
|
||||
def url_for_checked(self, /) -> str:
|
||||
# Different URL for a minifigure part
|
||||
if self.minifigure is not None:
|
||||
figure = self.minifigure.fields.figure
|
||||
# Different URL for individual minifigure part
|
||||
if self.individual_minifigure is not None:
|
||||
return url_for(
|
||||
'individual_minifigure.checked_part',
|
||||
id=self.individual_minifigure.fields.id,
|
||||
part=self.fields.part,
|
||||
color=self.fields.color,
|
||||
spare=self.fields.spare,
|
||||
)
|
||||
# Different URL for a set minifigure part
|
||||
elif self.minifigure is not None:
|
||||
return url_for(
|
||||
'set.checked_part',
|
||||
id=self.fields.id,
|
||||
figure=self.minifigure.fields.figure,
|
||||
part=self.fields.part,
|
||||
color=self.fields.color,
|
||||
spare=self.fields.spare,
|
||||
)
|
||||
# Set part
|
||||
else:
|
||||
figure = None
|
||||
|
||||
return url_for(
|
||||
'set.checked_part',
|
||||
id=self.fields.id,
|
||||
figure=figure,
|
||||
part=self.fields.part,
|
||||
color=self.fields.color,
|
||||
spare=self.fields.spare,
|
||||
)
|
||||
return url_for(
|
||||
'set.checked_part',
|
||||
id=self.fields.id,
|
||||
figure=None,
|
||||
part=self.fields.part,
|
||||
color=self.fields.color,
|
||||
spare=self.fields.spare,
|
||||
)
|
||||
|
||||
# Update a problematic part
|
||||
def update_problem(self, problem: str, json: Any | None, /) -> int:
|
||||
@@ -249,20 +317,67 @@ class BrickPart(RebrickablePart):
|
||||
|
||||
return amount
|
||||
|
||||
# Update a problematic part for individual minifigure
|
||||
def update_problem_individual_minifigure(self, problem: str, json: Any | None, /) -> int:
|
||||
amount: str | int = json.get('value', '') # type: ignore
|
||||
|
||||
# We need a positive integer
|
||||
try:
|
||||
if amount == '':
|
||||
amount = 0
|
||||
|
||||
amount = int(amount)
|
||||
|
||||
if amount < 0:
|
||||
amount = 0
|
||||
except Exception:
|
||||
raise ErrorException('"{amount}" is not a valid integer'.format(
|
||||
amount=amount
|
||||
))
|
||||
|
||||
if amount < 0:
|
||||
raise ErrorException('Cannot set a negative amount')
|
||||
|
||||
setattr(self.fields, problem, amount)
|
||||
|
||||
BrickSQL().execute_and_commit(
|
||||
'individual_minifigure/part/update/{problem}'.format(problem=problem),
|
||||
parameters=self.sql_parameters()
|
||||
)
|
||||
|
||||
return amount
|
||||
|
||||
# Compute the url for problematic part
|
||||
def url_for_problem(self, problem: str, /) -> str:
|
||||
# Different URL for a minifigure part
|
||||
if self.minifigure is not None:
|
||||
figure = self.minifigure.fields.figure
|
||||
# Different URL for individual minifigure part
|
||||
if self.individual_minifigure is not None:
|
||||
return url_for(
|
||||
'individual_minifigure.problem_part',
|
||||
id=self.individual_minifigure.fields.id,
|
||||
part=self.fields.part,
|
||||
color=self.fields.color,
|
||||
spare=self.fields.spare,
|
||||
problem=problem,
|
||||
)
|
||||
# Different URL for set minifigure part
|
||||
elif self.minifigure is not None:
|
||||
return url_for(
|
||||
'set.problem_part',
|
||||
id=self.fields.id,
|
||||
figure=self.minifigure.fields.figure,
|
||||
part=self.fields.part,
|
||||
color=self.fields.color,
|
||||
spare=self.fields.spare,
|
||||
problem=problem,
|
||||
)
|
||||
# Set part
|
||||
else:
|
||||
figure = None
|
||||
|
||||
return url_for(
|
||||
'set.problem_part',
|
||||
id=self.fields.id,
|
||||
figure=figure,
|
||||
part=self.fields.part,
|
||||
color=self.fields.color,
|
||||
spare=self.fields.spare,
|
||||
problem=problem,
|
||||
return url_for(
|
||||
'set.problem_part',
|
||||
id=self.fields.id,
|
||||
figure=None,
|
||||
part=self.fields.part,
|
||||
color=self.fields.color,
|
||||
spare=self.fields.spare,
|
||||
problem=problem,
|
||||
)
|
||||
|
||||
@@ -19,6 +19,7 @@ logger = logging.getLogger(__name__)
|
||||
class BrickPartList(BrickRecordList[BrickPart]):
|
||||
brickset: 'BrickSet | None'
|
||||
minifigure: 'BrickMinifigure | None'
|
||||
individual_minifigure: 'IndividualMinifigure | None'
|
||||
order: str
|
||||
|
||||
# Queries
|
||||
@@ -57,8 +58,8 @@ class BrickPartList(BrickRecordList[BrickPart]):
|
||||
|
||||
return self
|
||||
|
||||
# Load all parts with filters (owner, color, theme, year)
|
||||
def all_filtered(self, owner_id: str | None = None, color_id: str | None = None, theme_id: str | None = None, year: str | None = None, /) -> Self:
|
||||
# Load all parts with filters (owner, color, theme, year, individuals)
|
||||
def all_filtered(self, owner_id: str | None = None, color_id: str | None = None, theme_id: str | None = None, year: str | None = None, individuals_filter: str | None = None, /) -> Self:
|
||||
# Save the filter parameters
|
||||
if owner_id is not None:
|
||||
self.fields.owner_id = owner_id
|
||||
@@ -80,6 +81,8 @@ class BrickPartList(BrickRecordList[BrickPart]):
|
||||
context['theme_id'] = theme_id
|
||||
if year and year != 'all':
|
||||
context['year'] = year
|
||||
if individuals_filter and individuals_filter == 'only':
|
||||
context['individuals_filter'] = True
|
||||
|
||||
# Load the parts from the database
|
||||
self.list(override_query=query, **context)
|
||||
@@ -93,6 +96,7 @@ class BrickPartList(BrickRecordList[BrickPart]):
|
||||
color_id: str | None = None,
|
||||
theme_id: str | None = None,
|
||||
year: str | None = None,
|
||||
individuals_filter: str | None = None,
|
||||
search_query: str | None = None,
|
||||
page: int = 1,
|
||||
per_page: int = 50,
|
||||
@@ -113,6 +117,8 @@ class BrickPartList(BrickRecordList[BrickPart]):
|
||||
filter_context['theme_id'] = theme_id
|
||||
if year and year != 'all':
|
||||
filter_context['year'] = year
|
||||
if individuals_filter and individuals_filter == 'only':
|
||||
filter_context['individuals_filter'] = True
|
||||
if search_query:
|
||||
filter_context['search_query'] = search_query
|
||||
# Hide spare parts from display if configured
|
||||
@@ -165,6 +171,11 @@ class BrickPartList(BrickRecordList[BrickPart]):
|
||||
else:
|
||||
minifigure = None
|
||||
|
||||
if hasattr(self, 'individual_minifigure'):
|
||||
individual_minifigure = self.individual_minifigure
|
||||
else:
|
||||
individual_minifigure = None
|
||||
|
||||
# Prepare template context for filtering
|
||||
context_vars = {}
|
||||
if hasattr(self.fields, 'owner_id') and self.fields.owner_id is not None:
|
||||
@@ -188,6 +199,7 @@ class BrickPartList(BrickRecordList[BrickPart]):
|
||||
part = BrickPart(
|
||||
brickset=brickset,
|
||||
minifigure=minifigure,
|
||||
individual_minifigure=individual_minifigure,
|
||||
record=record,
|
||||
)
|
||||
|
||||
@@ -234,6 +246,24 @@ class BrickPartList(BrickRecordList[BrickPart]):
|
||||
|
||||
return self
|
||||
|
||||
# Load parts from an individual minifigure instance
|
||||
def from_individual_minifigure(
|
||||
self,
|
||||
individual_minifigure: 'IndividualMinifigure',
|
||||
/,
|
||||
) -> Self:
|
||||
from .individual_minifigure import IndividualMinifigure
|
||||
|
||||
# Save the individual minifigure reference
|
||||
self.individual_minifigure = individual_minifigure
|
||||
|
||||
# Load the parts for this individual minifigure instance
|
||||
self.list(
|
||||
override_query='individual_minifigure/part/list/from_instance'
|
||||
)
|
||||
|
||||
return self
|
||||
|
||||
# Load generic parts from a print
|
||||
def from_print(
|
||||
self,
|
||||
@@ -369,6 +399,10 @@ class BrickPartList(BrickRecordList[BrickPart]):
|
||||
if self.brickset is not None:
|
||||
parameters['id'] = self.brickset.fields.id
|
||||
|
||||
# Use the individual minifigure ID if present
|
||||
if hasattr(self, 'individual_minifigure') and self.individual_minifigure is not None:
|
||||
parameters['id'] = self.individual_minifigure.fields.id
|
||||
|
||||
# Use the minifigure number if present,
|
||||
if self.minifigure is not None:
|
||||
parameters['figure'] = self.minifigure.fields.figure
|
||||
|
||||
@@ -14,7 +14,6 @@ if TYPE_CHECKING:
|
||||
class RebrickableMinifigure(BrickRecord):
|
||||
brickset: 'BrickSet | None'
|
||||
|
||||
# Queries
|
||||
select_query: str = 'rebrickable/minifigure/select'
|
||||
insert_query: str = 'rebrickable/minifigure/insert'
|
||||
|
||||
@@ -27,10 +26,8 @@ class RebrickableMinifigure(BrickRecord):
|
||||
):
|
||||
super().__init__()
|
||||
|
||||
# Save the brickset
|
||||
self.brickset = brickset
|
||||
|
||||
# Ingest the record if it has one
|
||||
if record is not None:
|
||||
self.ingest(record)
|
||||
|
||||
@@ -62,7 +59,6 @@ class RebrickableMinifigure(BrickRecord):
|
||||
|
||||
return parameters
|
||||
|
||||
# Self url
|
||||
def url(self, /) -> str:
|
||||
return url_for(
|
||||
'minifigure.details',
|
||||
@@ -89,17 +85,24 @@ class RebrickableMinifigure(BrickRecord):
|
||||
if current_app.config['REBRICKABLE_LINKS']:
|
||||
try:
|
||||
return current_app.config['REBRICKABLE_LINK_MINIFIGURE_PATTERN'].format( # noqa: E501
|
||||
number=self.fields.figure,
|
||||
figure=self.fields.figure,
|
||||
)
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
return ''
|
||||
|
||||
# Compute the url for the bricklink page
|
||||
# Note: BrickLink uses different minifigure IDs than Rebrickable (e.g., 'adv010' vs 'fig-000359')
|
||||
# Rebrickable API doesn't provide BrickLink minifigure IDs, so we can't generate valid links
|
||||
def url_for_bricklink(self, /) -> str:
|
||||
# BrickLink links disabled for minifigures - no ID mapping available
|
||||
# Left function for later, if I find a way to implement it.
|
||||
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 {
|
||||
|
||||
@@ -67,8 +67,11 @@ class RebrickablePart(BrickRecord):
|
||||
def sql_parameters(self, /) -> dict[str, Any]:
|
||||
parameters = super().sql_parameters()
|
||||
|
||||
# Individual minifigure id takes precedence
|
||||
if hasattr(self, 'individual_minifigure') and self.individual_minifigure is not None:
|
||||
parameters['id'] = self.individual_minifigure.fields.id
|
||||
# Set id
|
||||
if self.brickset is not None:
|
||||
elif self.brickset is not None:
|
||||
parameters['id'] = self.brickset.fields.id
|
||||
|
||||
# Use the minifigure number if present,
|
||||
|
||||
@@ -95,6 +95,18 @@ class RebrickableSet(BrickRecord):
|
||||
socket.auto_progress(message='Parsing set number')
|
||||
set = parse_set(str(data['set']))
|
||||
|
||||
# Check if this is actually a minifigure (starts with fig-)
|
||||
# If so, redirect to the minifigure handler
|
||||
if set.startswith('fig-'):
|
||||
from .individual_minifigure import IndividualMinifigure
|
||||
# Transform data: minifigure handler expects 'figure' key instead of 'set'
|
||||
minifig_data = data.copy()
|
||||
minifig_data['figure'] = minifig_data.pop('set')
|
||||
if from_download:
|
||||
return IndividualMinifigure().download(socket, minifig_data)
|
||||
else:
|
||||
return IndividualMinifigure().load(socket, minifig_data)
|
||||
|
||||
socket.auto_progress(
|
||||
message='Set {set}: loading from Rebrickable'.format(
|
||||
set=set,
|
||||
|
||||
@@ -36,6 +36,7 @@ class BrickSetList(BrickRecordList[BrickSet]):
|
||||
using_minifigure_query: str = 'set/list/using_minifigure'
|
||||
using_part_query: str = 'set/list/using_part'
|
||||
using_storage_query: str = 'set/list/using_storage'
|
||||
using_purchase_location_query: str = 'set/list/using_purchase_location'
|
||||
|
||||
def __init__(self, /):
|
||||
super().__init__()
|
||||
@@ -678,6 +679,16 @@ class BrickSetList(BrickRecordList[BrickSet]):
|
||||
|
||||
return self
|
||||
|
||||
# Sets using a purchase location
|
||||
def using_purchase_location(self, purchase_location: BrickSetPurchaseLocation, /) -> Self:
|
||||
# Save the parameters to the fields
|
||||
self.fields.purchase_location = purchase_location.fields.id
|
||||
|
||||
# Load the sets from the database
|
||||
self.list(override_query=self.using_purchase_location_query)
|
||||
|
||||
return self
|
||||
|
||||
|
||||
# Helper to build the metadata lists
|
||||
def set_metadata_lists(
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
from .metadata import BrickMetadata
|
||||
|
||||
from flask import url_for
|
||||
|
||||
|
||||
# Lego set purchase location metadata
|
||||
class BrickSetPurchaseLocation(BrickMetadata):
|
||||
@@ -11,3 +13,10 @@ class BrickSetPurchaseLocation(BrickMetadata):
|
||||
select_query: str = 'set/metadata/purchase_location/select'
|
||||
update_field_query: str = 'set/metadata/purchase_location/update/field'
|
||||
update_set_value_query: str = 'set/metadata/purchase_location/update/value'
|
||||
|
||||
# Self url
|
||||
def url(self, /) -> str:
|
||||
return url_for(
|
||||
'purchase_location.details',
|
||||
id=self.fields.id,
|
||||
)
|
||||
|
||||
@@ -18,13 +18,22 @@ logger = logging.getLogger(__name__)
|
||||
MESSAGES: Final[dict[str, str]] = {
|
||||
'COMPLETE': 'complete',
|
||||
'CONNECT': 'connect',
|
||||
'CREATE_LOT': 'create_lot',
|
||||
'CREATE_BULK_INDIVIDUAL_PARTS': 'create_bulk_individual_parts',
|
||||
'DISCONNECT': 'disconnect',
|
||||
'DOWNLOAD_INSTRUCTIONS': 'download_instructions',
|
||||
'DOWNLOAD_PEERON_PAGES': 'download_peeron_pages',
|
||||
'FAIL': 'fail',
|
||||
'IMPORT_MINIFIGURE': 'import_minifigure',
|
||||
'IMPORT_SET': 'import_set',
|
||||
'LOAD_MINIFIGURE': 'load_minifigure',
|
||||
'LOAD_PART': 'load_part',
|
||||
'LOAD_PART_COLORS': 'load_part_colors',
|
||||
'LOAD_PEERON_PAGES': 'load_peeron_pages',
|
||||
'LOAD_SET': 'load_set',
|
||||
'MINIFIGURE_LOADED': 'minifigure_loaded',
|
||||
'PART_COLORS_LOADED': 'part_colors_loaded',
|
||||
'PART_LOADED': 'part_loaded',
|
||||
'PROGRESS': 'progress',
|
||||
'SET_LOADED': 'set_loaded',
|
||||
}
|
||||
@@ -228,6 +237,67 @@ class BrickSocket(object):
|
||||
|
||||
BrickSet().load(self, data)
|
||||
|
||||
@self.socket.on(MESSAGES['IMPORT_MINIFIGURE'], namespace=self.namespace)
|
||||
@rebrickable_socket(self)
|
||||
def import_minifigure(data: dict[str, Any], /) -> None:
|
||||
logger.debug('Socket: IMPORT_MINIFIGURE={data} (from: {fr})'.format(
|
||||
data=data,
|
||||
fr=request.sid, # type: ignore
|
||||
))
|
||||
|
||||
from .individual_minifigure import IndividualMinifigure
|
||||
IndividualMinifigure().download(self, data)
|
||||
|
||||
@self.socket.on(MESSAGES['LOAD_MINIFIGURE'], namespace=self.namespace)
|
||||
def load_minifigure(data: dict[str, Any], /) -> None:
|
||||
logger.debug('Socket: LOAD_MINIFIGURE={data} (from: {fr})'.format(
|
||||
data=data,
|
||||
fr=request.sid, # type: ignore
|
||||
))
|
||||
|
||||
from .individual_minifigure import IndividualMinifigure
|
||||
IndividualMinifigure().load(self, data)
|
||||
|
||||
@self.socket.on(MESSAGES['LOAD_PART'], namespace=self.namespace)
|
||||
def load_part(data: dict[str, Any], /) -> None:
|
||||
logger.debug('Socket: LOAD_PART={data} (from: {fr})'.format(
|
||||
data=data,
|
||||
fr=request.sid, # type: ignore
|
||||
))
|
||||
|
||||
from .individual_part import IndividualPart
|
||||
IndividualPart().add(self, data)
|
||||
|
||||
@self.socket.on(MESSAGES['LOAD_PART_COLORS'], namespace=self.namespace)
|
||||
def load_part_colors(data: dict[str, Any], /) -> None:
|
||||
logger.debug('Socket: LOAD_PART_COLORS={data} (from: {fr})'.format(
|
||||
data=data,
|
||||
fr=request.sid, # type: ignore
|
||||
))
|
||||
|
||||
from .individual_part import IndividualPart
|
||||
IndividualPart().load_colors(self, data)
|
||||
|
||||
@self.socket.on(MESSAGES['CREATE_LOT'], namespace=self.namespace)
|
||||
@rebrickable_socket(self)
|
||||
def create_lot(data: dict[str, Any], /) -> None:
|
||||
logger.debug('Socket: CREATE_LOT (from: {fr})'.format(
|
||||
fr=request.sid, # type: ignore
|
||||
))
|
||||
|
||||
from .individual_part_lot import IndividualPartLot
|
||||
IndividualPartLot().create(self, data)
|
||||
|
||||
@self.socket.on(MESSAGES['CREATE_BULK_INDIVIDUAL_PARTS'], namespace=self.namespace)
|
||||
@rebrickable_socket(self)
|
||||
def create_bulk_individual_parts(data: dict[str, Any], /) -> None:
|
||||
logger.debug('Socket: CREATE_BULK_INDIVIDUAL_PARTS (from: {fr})'.format(
|
||||
fr=request.sid, # type: ignore
|
||||
))
|
||||
|
||||
from .individual_part import IndividualPart
|
||||
IndividualPart().create_bulk(self, data)
|
||||
|
||||
# Update the progress auto-incrementing
|
||||
def auto_progress(
|
||||
self,
|
||||
|
||||
@@ -53,17 +53,18 @@ class BrickStatistics:
|
||||
return [dict(row) for row in results]
|
||||
|
||||
def get_financial_summary(self) -> dict[str, Any]:
|
||||
"""Get financial summary from overview statistics"""
|
||||
"""Get financial summary from overview statistics (includes all item types)"""
|
||||
overview = self.get_overview()
|
||||
return {
|
||||
'total_cost': overview.get('total_cost') or 0,
|
||||
'average_cost': overview.get('average_cost') or 0,
|
||||
'minimum_cost': overview.get('minimum_cost') or 0,
|
||||
'maximum_cost': overview.get('maximum_cost') or 0,
|
||||
'total_cost': overview.get('combined_total_cost') or 0,
|
||||
'average_cost': overview.get('combined_average_cost') or 0,
|
||||
'minimum_cost': overview.get('combined_minimum_cost') or 0,
|
||||
'maximum_cost': overview.get('combined_maximum_cost') or 0,
|
||||
'items_with_price': overview.get('total_items_with_price') or 0,
|
||||
'sets_with_price': overview.get('sets_with_price') or 0,
|
||||
'total_sets': overview.get('total_sets') or 0,
|
||||
'percentage_with_price': round(
|
||||
((overview.get('sets_with_price') or 0) / max((overview.get('total_sets') or 0), 1)) * 100, 1
|
||||
((overview.get('total_items_with_price') or 0) / max((overview.get('total_sets') or 0), 1)) * 100, 1
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user