From 0ad9cff7a07acc0ed5a0ce4420addb6e8c81851a Mon Sep 17 00:00:00 2001 From: Gregoo Date: Wed, 29 Jan 2025 19:28:00 +0100 Subject: [PATCH] Set theme override --- bricktracker/rebrickable_set.py | 10 ++++- bricktracker/set.py | 63 ++++++++++++++++++++++++++- bricktracker/sql/set/base/base.sql | 1 + bricktracker/sql/set/update/theme.sql | 3 ++ bricktracker/theme.py | 10 ++--- bricktracker/theme_list.py | 9 ++-- bricktracker/views/set.py | 19 ++++++++ templates/set/management.html | 8 +++- 8 files changed, 111 insertions(+), 12 deletions(-) create mode 100644 bricktracker/sql/set/update/theme.sql diff --git a/bricktracker/rebrickable_set.py b/bricktracker/rebrickable_set.py index fbf10f1..d2878d6 100644 --- a/bricktracker/rebrickable_set.py +++ b/bricktracker/rebrickable_set.py @@ -11,10 +11,10 @@ from .parser import parse_set from .rebrickable import Rebrickable from .rebrickable_image import RebrickableImage from .record import BrickRecord +from .theme import BrickTheme from .theme_list import BrickThemeList if TYPE_CHECKING: from .socket import BrickSocket - from .theme import BrickTheme logger = logging.getLogger(__name__) @@ -66,7 +66,13 @@ class RebrickableSet(BrickRecord): if not hasattr(self.fields, 'theme_id'): self.fields.theme_id = 0 - self.theme = BrickThemeList().get(self.fields.theme_id) + if not hasattr(self.fields, 'theme_name'): + self.fields.theme_name = None + + self.theme = BrickThemeList().get( + str(self.fields.theme_id), + name=self.fields.theme_name + ) # Resolve instructions if self.resolve_instructions: diff --git a/bricktracker/set.py b/bricktracker/set.py index 28f0341..893acc0 100644 --- a/bricktracker/set.py +++ b/bricktracker/set.py @@ -1,11 +1,12 @@ import logging +from sqlite3 import Row import traceback from typing import Any, Self, TYPE_CHECKING from uuid import uuid4 from flask import current_app, url_for -from .exceptions import DatabaseException, NotFoundException +from .exceptions import DatabaseException, ErrorException, NotFoundException from .minifigure_list import BrickMinifigureList from .part_list import BrickPartList from .rebrickable_set import RebrickableSet @@ -121,6 +122,29 @@ class BrickSet(RebrickableSet): return True + # Ingest a set + def ingest(self, record: Row | dict[str, Any], /): + # Super charge the record with theme override + if 'theme' in record.keys() and record['theme'] is not None: + if isinstance(record, Row): + record = dict(record) + + record['theme_id'] = record['theme'] + record['theme_name'] = record['theme'] + + super().ingest(record) + + # A identifier for HTML component + def html_id(self, prefix: str | None = None, /) -> str: + components: list[str] = [] + + if prefix is not None: + components.append(prefix) + + components.append(self.fields.id) + + return '-'.join(components) + # Minifigures def minifigures(self, /) -> BrickMinifigureList: return BrickMinifigureList().from_set(self) @@ -185,6 +209,36 @@ class BrickSet(RebrickableSet): id=self.fields.id, )) + # Update theme + def update_theme(self, json: Any | None, /) -> None: + theme: str | None = json.get('value', '') # type: ignore + + # We need a string + try: + theme = str(theme) + theme = theme.strip() + except Exception: + raise ErrorException('"{theme}" is not a valid string'.format( + theme=theme + )) + + if theme == '': + theme = None + + self.fields.theme = theme + + # Update the status + rows, _ = BrickSQL().execute_and_commit( + 'set/update/theme', + parameters=self.sql_parameters() + ) + + if rows != 1: + raise DatabaseException('Could not update the theme override for set {set} ({id})'.format( # noqa: E501 + set=self.fields.set, + id=self.fields.id, + )) + # Self url def url(self, /) -> str: return url_for('set.details', id=self.fields.id) @@ -217,3 +271,10 @@ class BrickSet(RebrickableSet): 'set.refresh', id=self.fields.id, ) + + # Compute the url for the theme override + def url_for_theme(self, /) -> str: + return url_for( + 'set.update_theme', + id=self.fields.id, + ) diff --git a/bricktracker/sql/set/base/base.sql b/bricktracker/sql/set/base/base.sql index 8b1f4c8..7bc1bc3 100644 --- a/bricktracker/sql/set/base/base.sql +++ b/bricktracker/sql/set/base/base.sql @@ -1,5 +1,6 @@ SELECT {% block id %}{% endblock %} + "bricktracker_sets"."theme", "rebrickable_sets"."set", "rebrickable_sets"."number", "rebrickable_sets"."version", diff --git a/bricktracker/sql/set/update/theme.sql b/bricktracker/sql/set/update/theme.sql new file mode 100644 index 0000000..1d884ed --- /dev/null +++ b/bricktracker/sql/set/update/theme.sql @@ -0,0 +1,3 @@ +UPDATE "bricktracker_sets" +SET "theme" = :theme +WHERE "bricktracker_sets"."id" IS NOT DISTINCT FROM :id diff --git a/bricktracker/theme.py b/bricktracker/theme.py index 3ee1068..df677f4 100644 --- a/bricktracker/theme.py +++ b/bricktracker/theme.py @@ -1,14 +1,14 @@ # Lego set theme class BrickTheme(object): - id: int + id: str name: str - parent: int | None + parent: str | None - def __init__(self, id: str | int, name: str, parent: str | None = None, /): - self.id = int(id) + def __init__(self, id: str, name: str, parent: str | None = None, /): + self.id = id self.name = name if parent is not None and parent != '': - self.parent = int(parent) + self.parent = parent else: self.parent = None diff --git a/bricktracker/theme_list.py b/bricktracker/theme_list.py index 22cac8e..af1667e 100644 --- a/bricktracker/theme_list.py +++ b/bricktracker/theme_list.py @@ -17,7 +17,7 @@ logger = logging.getLogger(__name__) # Lego sets themes class BrickThemeList(object): - themes: dict[int, BrickTheme] + themes: dict[str, BrickTheme] mtime: datetime | None size: int | None exception: Exception | None @@ -57,12 +57,15 @@ class BrickThemeList(object): BrickThemeList.mtime = None # Get a theme - def get(self, id: int, /) -> BrickTheme: + def get(self, id: str, /, *, name: str | None = None) -> BrickTheme: # Seed a fake entry if missing if id not in self.themes: + if name is None: + name = 'Unknown ({id})'.format(id=id) + BrickThemeList.themes[id] = BrickTheme( id, - 'Unknown ({id})'.format(id=id) + name, ) return self.themes[id] diff --git a/bricktracker/views/set.py b/bricktracker/views/set.py index 809d46b..59289ff 100644 --- a/bricktracker/views/set.py +++ b/bricktracker/views/set.py @@ -167,3 +167,22 @@ def refresh(*, id: str) -> str: namespace=current_app.config['SOCKET_NAMESPACE'], messages=MESSAGES ) + + +# Change the theme override +@set_page.route('//theme', methods=['POST']) +@login_required +@exception_handler(__file__, json=True) +def update_theme(*, id: str) -> Response: + brickset = BrickSet().select_light(id) + + brickset.update_theme(request.json) + + # Info + logger.info('Set {set} ({id}): theme override changed to "{theme}"'.format( # noqa: E501 + set=brickset.fields.set, + id=brickset.fields.id, + theme=brickset.fields.theme, + )) + + return jsonify({'value': brickset.fields.theme}) diff --git a/templates/set/management.html b/templates/set/management.html index b27c9d0..b299972 100644 --- a/templates/set/management.html +++ b/templates/set/management.html @@ -1,5 +1,11 @@ {% if g.login.is_authenticated() %} -{{ accordion.header('Management', 'management', 'set-details', icon='settings-4-line', class='text-end') }} +{{ accordion.header('Management', 'management', 'set-details', expanded=true, icon='settings-4-line') }} +
Theme override
+

+ You can override the current theme ({{ badge.theme(item.theme.name, solo=solo, last=last) }}) with any string you want. + {{ form.input('Theme', item.fields.id, item.html_id('theme'), item.url_for_theme(), item.fields.theme, all=all, read_only=read_only) }} +

+
Data
Refresh the set data {{ accordion.footer() }} {% endif %}