From e232e2ab7f9d4953cad711994c9975f7cd5488b2 Mon Sep 17 00:00:00 2001 From: Gregoo Date: Mon, 20 Jan 2025 15:20:07 +0100 Subject: [PATCH 01/50] Don't store complex objects in Flash config that could mask existing config items, rather store the values and handle the actual list of conf differently --- app.py | 12 +++---- bricktracker/app.py | 4 +-- bricktracker/configuration.py | 1 + bricktracker/configuration_list.py | 44 ++++++++++++++++--------- bricktracker/instructions.py | 8 ++--- bricktracker/instructions_list.py | 5 ++- bricktracker/login.py | 6 ++-- bricktracker/minifigure.py | 8 ++--- bricktracker/minifigure_list.py | 4 +-- bricktracker/part.py | 12 +++---- bricktracker/part_list.py | 12 ++----- bricktracker/rebrickable.py | 4 +-- bricktracker/rebrickable_image.py | 16 ++++----- bricktracker/rebrickable_minifigures.py | 2 +- bricktracker/rebrickable_parts.py | 4 +-- bricktracker/rebrickable_set.py | 4 +-- bricktracker/retired_list.py | 10 +++--- bricktracker/set.py | 6 ++-- bricktracker/set_list.py | 4 +-- bricktracker/socket.py | 8 ++--- bricktracker/sql.py | 6 ++-- bricktracker/theme_list.py | 10 +++--- bricktracker/views/add.py | 8 ++--- bricktracker/views/admin.py | 6 ++-- bricktracker/views/instructions.py | 2 +- bricktracker/wish_list.py | 2 +- templates/add.html | 2 +- templates/admin.html | 2 +- templates/admin/database.html | 4 +-- templates/admin/instructions.html | 4 +-- templates/admin/retired.html | 2 +- templates/admin/theme.html | 2 +- templates/base.html | 4 +-- templates/index.html | 8 ++--- templates/instructions/upload.html | 4 +-- templates/macro/accordion.html | 2 +- templates/macro/table.html | 2 +- 37 files changed, 126 insertions(+), 118 deletions(-) diff --git a/app.py b/app.py index 2077083..8cea2ef 100644 --- a/app.py +++ b/app.py @@ -20,19 +20,19 @@ setup_app(app) # Create the socket s = BrickSocket( app, - threaded=not app.config['NO_THREADED_SOCKET'].value, + threaded=not app.config['NO_THREADED_SOCKET'], ) if __name__ == '__main__': # Run the application logger.info('Starting BrickTracker on {host}:{port}'.format( - host=app.config['HOST'].value, - port=app.config['PORT'].value, + host=app.config['HOST'], + port=app.config['PORT'], )) s.socket.run( app, - host=app.config['HOST'].value, - debug=app.config['DEBUG'].value, - port=app.config['PORT'].value, + host=app.config['HOST'], + debug=app.config['DEBUG'], + port=app.config['PORT'], ) diff --git a/bricktracker/app.py b/bricktracker/app.py index 5ba4414..a1bfb27 100644 --- a/bricktracker/app.py +++ b/bricktracker/app.py @@ -28,7 +28,7 @@ def setup_app(app: Flask) -> None: BrickConfigurationList(app) # Set the logging level - if app.config['DEBUG'].value: + if app.config['DEBUG']: logging.basicConfig( stream=sys.stdout, level=logging.DEBUG, @@ -90,7 +90,7 @@ def setup_app(app: Flask) -> None: g.request_time = request_time # Register the timezone - g.timezone = ZoneInfo(current_app.config['TIMEZONE'].value) + g.timezone = ZoneInfo(current_app.config['TIMEZONE']) # Version g.version = __version__ diff --git a/bricktracker/configuration.py b/bricktracker/configuration.py index b9c3559..d5c561b 100644 --- a/bricktracker/configuration.py +++ b/bricktracker/configuration.py @@ -69,6 +69,7 @@ class BrickConfiguration(object): # Remove static prefix value = value.removeprefix('static/') + # Type casting if self.cast is not None: self.value = self.cast(value) else: diff --git a/bricktracker/configuration_list.py b/bricktracker/configuration_list.py index 0e93a88..3293d85 100644 --- a/bricktracker/configuration_list.py +++ b/bricktracker/configuration_list.py @@ -1,46 +1,60 @@ +import logging from typing import Generator -from flask import current_app, Flask +from flask import Flask from .config import CONFIG from .configuration import BrickConfiguration from .exceptions import ConfigurationMissingException +logger = logging.getLogger(__name__) + # Application configuration class BrickConfigurationList(object): app: Flask + configurations: dict[str, BrickConfiguration] # Load configuration def __init__(self, app: Flask, /): self.app = app - # Process all configuration items - for config in CONFIG: - item = BrickConfiguration(**config) - self.app.config[item.name] = item + # Load the configurations only there is none already loaded + configurations = getattr(self, 'configurations', None) + + if configurations is None: + logger.info('Loading configuration variables') + + BrickConfigurationList.configurations = {} + + # Process all configuration items + for config in CONFIG: + item = BrickConfiguration(**config) + + # Store in the list + BrickConfigurationList.configurations[item.name] = item + + # Only store the value in the app to avoid breaking any + # existing variables + self.app.config[item.name] = item.value # Check whether a str configuration is set @staticmethod def error_unless_is_set(name: str): - config: BrickConfiguration = current_app.config[name] + configuration = BrickConfigurationList.configurations[name] - if config.value is None or config.value == '': + if configuration.value is None or configuration.value == '': raise ConfigurationMissingException( '{name} must be defined (using the {environ} environment variable)'.format( # noqa: E501 - name=config.name, - environ=config.env_name + name=name, + environ=configuration.env_name ), ) # Get all the configuration items from the app config @staticmethod def list() -> Generator[BrickConfiguration, None, None]: - keys = list(current_app.config.keys()) - keys.sort() + keys = sorted(BrickConfigurationList.configurations.keys()) for name in keys: - config = current_app.config[name] - - if isinstance(config, BrickConfiguration): - yield config + yield BrickConfigurationList.configurations[name] diff --git a/bricktracker/instructions.py b/bricktracker/instructions.py index 6aaa050..622fdc3 100644 --- a/bricktracker/instructions.py +++ b/bricktracker/instructions.py @@ -39,7 +39,7 @@ class BrickInstructions(object): # Store the name and extension, check if extension is allowed self.name, self.extension = os.path.splitext(self.filename) self.extension = self.extension.lower() - self.allowed = self.extension in current_app.config['INSTRUCTIONS_ALLOWED_EXTENSIONS'].value # noqa: E501 + self.allowed = self.extension in current_app.config['INSTRUCTIONS_ALLOWED_EXTENSIONS'] # noqa: E501 # Placeholder self.brickset = None @@ -67,7 +67,7 @@ class BrickInstructions(object): # Display the time in a human format def human_time(self) -> str: return self.mtime.astimezone(g.timezone).strftime( - current_app.config['FILE_DATETIME_FORMAT'].value + current_app.config['FILE_DATETIME_FORMAT'] ) # Compute the path of an instruction file @@ -77,7 +77,7 @@ class BrickInstructions(object): return os.path.join( current_app.static_folder, # type: ignore - current_app.config['INSTRUCTIONS_FOLDER'].value, + current_app.config['INSTRUCTIONS_FOLDER'], filename ) @@ -118,7 +118,7 @@ class BrickInstructions(object): if not self.allowed: return '' - folder: str = current_app.config['INSTRUCTIONS_FOLDER'].value + folder: str = current_app.config['INSTRUCTIONS_FOLDER'] # Compute the path path = os.path.join(folder, self.filename) diff --git a/bricktracker/instructions_list.py b/bricktracker/instructions_list.py index 57329fe..4d447b9 100644 --- a/bricktracker/instructions_list.py +++ b/bricktracker/instructions_list.py @@ -36,7 +36,7 @@ class BrickInstructionsList(object): # Make a folder relative to static folder: str = os.path.join( current_app.static_folder, # type: ignore - current_app.config['INSTRUCTIONS_FOLDER'].value, + current_app.config['INSTRUCTIONS_FOLDER'], ) for file in os.scandir(folder): @@ -68,9 +68,8 @@ class BrickInstructionsList(object): for brickset in BrickSetList().generic().records: bricksets[brickset.fields.set_num] = brickset - # Return the files + # Inject the brickset if it exists for instruction in self.all.values(): - # Inject the brickset if it exists if ( instruction.allowed and instruction.number is not None and diff --git a/bricktracker/login.py b/bricktracker/login.py index 87e6683..2642464 100644 --- a/bricktracker/login.py +++ b/bricktracker/login.py @@ -12,7 +12,7 @@ class LoginManager(object): def __init__(self, app: Flask, /): # Setup basic authentication - app.secret_key = app.config['AUTHENTICATION_KEY'].value + app.secret_key = app.config['AUTHENTICATION_KEY'] manager = login_manager.LoginManager() manager.login_view = 'login.login' # type: ignore @@ -23,11 +23,11 @@ class LoginManager(object): def user_loader(*arg) -> LoginManager.User: return self.User( 'admin', - app.config['AUTHENTICATION_PASSWORD'].value + app.config['AUTHENTICATION_PASSWORD'] ) # If the password is unset, globally disable - app.config['LOGIN_DISABLED'] = app.config['AUTHENTICATION_PASSWORD'].value == '' # noqa: E501 + app.config['LOGIN_DISABLED'] = app.config['AUTHENTICATION_PASSWORD'] == '' # noqa: E501 # Tells whether the user is authenticated, meaning: # - Authentication disabled diff --git a/bricktracker/minifigure.py b/bricktracker/minifigure.py index 7a7adf6..afe5463 100644 --- a/bricktracker/minifigure.py +++ b/bricktracker/minifigure.py @@ -119,7 +119,7 @@ class BrickMinifigure(BrickRecord): # Compute the url for minifigure part image def url_for_image(self, /) -> str: - if not current_app.config['USE_REMOTE_IMAGES'].value: + if not current_app.config['USE_REMOTE_IMAGES']: if self.fields.set_img_url is None: file = RebrickableImage.nil_minifigure_name() else: @@ -128,15 +128,15 @@ class BrickMinifigure(BrickRecord): return RebrickableImage.static_url(file, 'MINIFIGURES_FOLDER') else: if self.fields.set_img_url is None: - return current_app.config['REBRICKABLE_IMAGE_NIL_MINIFIGURE'].value # noqa: E501 + 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'].value: + if current_app.config['REBRICKABLE_LINKS']: try: - return current_app.config['REBRICKABLE_LINK_MINIFIGURE_PATTERN'].value.format( # noqa: E501 + return current_app.config['REBRICKABLE_LINK_MINIFIGURE_PATTERN'].format( # noqa: E501 number=self.fields.fig_num.lower(), ) except Exception: diff --git a/bricktracker/minifigure_list.py b/bricktracker/minifigure_list.py index dd00c2d..971fc6f 100644 --- a/bricktracker/minifigure_list.py +++ b/bricktracker/minifigure_list.py @@ -27,7 +27,7 @@ class BrickMinifigureList(BrickRecordList[BrickMinifigure]): self.brickset = None # Store the order for this list - self.order = current_app.config['MINIFIGURES_DEFAULT_ORDER'].value + self.order = current_app.config['MINIFIGURES_DEFAULT_ORDER'] # Load all minifigures def all(self, /) -> Self: @@ -44,7 +44,7 @@ class BrickMinifigureList(BrickRecordList[BrickMinifigure]): # Last added minifigure def last(self, /, limit: int = 6) -> Self: # Randomize - if current_app.config['RANDOM'].value: + if current_app.config['RANDOM']: order = 'RANDOM()' else: order = 'minifigures.rowid DESC' diff --git a/bricktracker/part.py b/bricktracker/part.py index e14b6c0..dd71bbb 100644 --- a/bricktracker/part.py +++ b/bricktracker/part.py @@ -190,9 +190,9 @@ class BrickPart(BrickRecord): # Compute the url for the bricklink page def url_for_bricklink(self, /) -> str: - if current_app.config['BRICKLINK_LINKS'].value: + if current_app.config['BRICKLINK_LINKS']: try: - return current_app.config['BRICKLINK_LINK_PART_PATTERN'].value.format( # noqa: E501 + return current_app.config['BRICKLINK_LINK_PART_PATTERN'].format( # noqa: E501 number=self.fields.part_num, ) except Exception: @@ -202,7 +202,7 @@ class BrickPart(BrickRecord): # Compute the url for the part image def url_for_image(self, /) -> str: - if not current_app.config['USE_REMOTE_IMAGES'].value: + if not current_app.config['USE_REMOTE_IMAGES']: if self.fields.part_img_url is None: file = RebrickableImage.nil_name() else: @@ -211,7 +211,7 @@ class BrickPart(BrickRecord): return RebrickableImage.static_url(file, 'PARTS_FOLDER') else: if self.fields.part_img_url is None: - return current_app.config['REBRICKABLE_IMAGE_NIL'].value + return current_app.config['REBRICKABLE_IMAGE_NIL'] else: return self.fields.part_img_url @@ -234,9 +234,9 @@ class BrickPart(BrickRecord): # Compute the url for the rebrickable page def url_for_rebrickable(self, /) -> str: - if current_app.config['REBRICKABLE_LINKS'].value: + if current_app.config['REBRICKABLE_LINKS']: try: - return current_app.config['REBRICKABLE_LINK_PART_PATTERN'].value.format( # noqa: E501 + return current_app.config['REBRICKABLE_LINK_PART_PATTERN'].format( # noqa: E501 number=self.fields.part_num, color=self.fields.color_id, ) diff --git a/bricktracker/part_list.py b/bricktracker/part_list.py index d6e6945..3932d8e 100644 --- a/bricktracker/part_list.py +++ b/bricktracker/part_list.py @@ -30,7 +30,7 @@ class BrickPartList(BrickRecordList[BrickPart]): self.minifigure = None # Store the order for this list - self.order = current_app.config['PARTS_DEFAULT_ORDER'].value + self.order = current_app.config['PARTS_DEFAULT_ORDER'] # Load all parts def all(self, /) -> Self: @@ -63,10 +63,7 @@ class BrickPartList(BrickRecordList[BrickPart]): record=record, ) - if ( - current_app.config['SKIP_SPARE_PARTS'].value and - part.fields.is_spare - ): + if current_app.config['SKIP_SPARE_PARTS'] and part.fields.is_spare: continue self.records.append(part) @@ -92,10 +89,7 @@ class BrickPartList(BrickRecordList[BrickPart]): record=record, ) - if ( - current_app.config['SKIP_SPARE_PARTS'].value and - part.fields.is_spare - ): + if current_app.config['SKIP_SPARE_PARTS'] and part.fields.is_spare: continue self.records.append(part) diff --git a/bricktracker/rebrickable.py b/bricktracker/rebrickable.py index 3f34fdd..312797a 100644 --- a/bricktracker/rebrickable.py +++ b/bricktracker/rebrickable.py @@ -77,7 +77,7 @@ class Rebrickable(Generic[T]): # Bootstrap a first set of parameters parameters: dict[str, Any] | None = { - 'page_size': current_app.config['REBRICKABLE_PAGE_SIZE'].value, + 'page_size': current_app.config['REBRICKABLE_PAGE_SIZE'], } # Read all pages @@ -115,7 +115,7 @@ class Rebrickable(Generic[T]): # Load from the API def load(self, /, parameters: dict[str, Any] = {}) -> dict[str, Any]: # Inject the API key - parameters['api_key'] = current_app.config['REBRICKABLE_API_KEY'].value, # noqa: E501 + parameters['api_key'] = current_app.config['REBRICKABLE_API_KEY'] try: return json.loads( diff --git a/bricktracker/rebrickable_image.py b/bricktracker/rebrickable_image.py index 513e4b2..d0a07ec 100644 --- a/bricktracker/rebrickable_image.py +++ b/bricktracker/rebrickable_image.py @@ -70,12 +70,12 @@ class RebrickableImage(object): # Return the folder depending on the objects provided def folder(self, /) -> str: if self.part is not None: - return current_app.config['PARTS_FOLDER'].value + return current_app.config['PARTS_FOLDER'] if self.minifigure is not None: - return current_app.config['MINIFIGURES_FOLDER'].value + return current_app.config['MINIFIGURES_FOLDER'] - return current_app.config['SETS_FOLDER'].value + return current_app.config['SETS_FOLDER'] # Return the id depending on the objects provided def id(self, /) -> str: @@ -105,13 +105,13 @@ class RebrickableImage(object): def url(self, /) -> str: if self.part is not None: if self.part.fields.part_img_url is None: - return current_app.config['REBRICKABLE_IMAGE_NIL'].value + return current_app.config['REBRICKABLE_IMAGE_NIL'] else: return self.part.fields.part_img_url if self.minifigure is not None: if self.minifigure.fields.set_img_url is None: - return current_app.config['REBRICKABLE_IMAGE_NIL_MINIFIGURE'].value # noqa: E501 + return current_app.config['REBRICKABLE_IMAGE_NIL_MINIFIGURE'] else: return self.minifigure.fields.set_img_url @@ -122,7 +122,7 @@ class RebrickableImage(object): def nil_name() -> str: filename, _ = os.path.splitext( os.path.basename( - urlparse(current_app.config['REBRICKABLE_IMAGE_NIL'].value).path # noqa: E501 + urlparse(current_app.config['REBRICKABLE_IMAGE_NIL']).path ) ) @@ -133,7 +133,7 @@ class RebrickableImage(object): def nil_minifigure_name() -> str: filename, _ = os.path.splitext( os.path.basename( - urlparse(current_app.config['REBRICKABLE_IMAGE_NIL_MINIFIGURE'].value).path # noqa: E501 + urlparse(current_app.config['REBRICKABLE_IMAGE_NIL_MINIFIGURE']).path # noqa: E501 ) ) @@ -142,7 +142,7 @@ class RebrickableImage(object): # Return the static URL for an image given a name and folder @staticmethod def static_url(name: str, folder_name: str) -> str: - folder: str = current_app.config[folder_name].value + folder: str = current_app.config[folder_name] # /!\ Everything is saved as .jpg, even if it came from a .png # not changing this behaviour. diff --git a/bricktracker/rebrickable_minifigures.py b/bricktracker/rebrickable_minifigures.py index b34284a..bcf165b 100644 --- a/bricktracker/rebrickable_minifigures.py +++ b/bricktracker/rebrickable_minifigures.py @@ -71,7 +71,7 @@ class RebrickableMinifigures(object): ) ) - if not current_app.config['USE_REMOTE_IMAGES'].value: + if not current_app.config['USE_REMOTE_IMAGES']: RebrickableImage( self.brickset, minifigure=minifigure diff --git a/bricktracker/rebrickable_parts.py b/bricktracker/rebrickable_parts.py index 1049b92..6ef9abb 100644 --- a/bricktracker/rebrickable_parts.py +++ b/bricktracker/rebrickable_parts.py @@ -76,7 +76,7 @@ class RebrickableParts(object): for index, part in enumerate(inventory): # Skip spare parts if ( - current_app.config['SKIP_SPARE_PARTS'].value and + current_app.config['SKIP_SPARE_PARTS'] and part.fields.is_spare ): continue @@ -104,7 +104,7 @@ class RebrickableParts(object): ) ) - if not current_app.config['USE_REMOTE_IMAGES'].value: + if not current_app.config['USE_REMOTE_IMAGES']: RebrickableImage( self.brickset, minifigure=self.minifigure, diff --git a/bricktracker/rebrickable_set.py b/bricktracker/rebrickable_set.py index b6ccf16..329c411 100644 --- a/bricktracker/rebrickable_set.py +++ b/bricktracker/rebrickable_set.py @@ -55,7 +55,7 @@ class RebrickableSet(object): # Insert into database brickset.insert(commit=False) - if not current_app.config['USE_REMOTE_IMAGES'].value: + if not current_app.config['USE_REMOTE_IMAGES']: RebrickableImage(brickset).download() # Load the inventory @@ -210,5 +210,5 @@ class RebrickableSet(object): # Insert into database brickwish.insert() - if not current_app.config['USE_REMOTE_IMAGES'].value: + if not current_app.config['USE_REMOTE_IMAGES']: RebrickableImage(brickwish).download() diff --git a/bricktracker/retired_list.py b/bricktracker/retired_list.py index e76a699..78c3a68 100644 --- a/bricktracker/retired_list.py +++ b/bricktracker/retired_list.py @@ -33,7 +33,7 @@ class BrickRetiredList(object): # Try to read the themes from a CSV file try: - with open(current_app.config['RETIRED_SETS_PATH'].value, newline='') as themes_file: # noqa: E501 + with open(current_app.config['RETIRED_SETS_PATH'], newline='') as themes_file: # noqa: E501 themes_reader = csv.reader(themes_file) # Ignore the header @@ -44,7 +44,7 @@ class BrickRetiredList(object): BrickRetiredList.retired[retired.number] = retired # File stats - stat = os.stat(current_app.config['RETIRED_SETS_PATH'].value) + stat = os.stat(current_app.config['RETIRED_SETS_PATH']) BrickRetiredList.size = stat.st_size BrickRetiredList.mtime = datetime.fromtimestamp(stat.st_mtime, tz=timezone.utc) # noqa: E501 @@ -79,7 +79,7 @@ class BrickRetiredList(object): def human_time(self) -> str: if self.mtime is not None: return self.mtime.astimezone(g.timezone).strftime( - current_app.config['FILE_DATETIME_FORMAT'].value + current_app.config['FILE_DATETIME_FORMAT'] ) else: return '' @@ -88,7 +88,7 @@ class BrickRetiredList(object): @staticmethod def update() -> None: response = requests.get( - current_app.config['RETIRED_SETS_FILE_URL'].value, + current_app.config['RETIRED_SETS_FILE_URL'], stream=True, ) @@ -99,7 +99,7 @@ class BrickRetiredList(object): content = gzip.GzipFile(fileobj=response.raw) - with open(current_app.config['RETIRED_SETS_PATH'].value, 'wb') as f: + with open(current_app.config['RETIRED_SETS_PATH'], 'wb') as f: copyfileobj(content, f) logger.info('Retired sets list updated') diff --git a/bricktracker/set.py b/bricktracker/set.py index 2f1778e..f2cb9f8 100644 --- a/bricktracker/set.py +++ b/bricktracker/set.py @@ -157,7 +157,7 @@ class BrickSet(BrickRecord): # Compute the url for the set image def url_for_image(self, /) -> str: - if not current_app.config['USE_REMOTE_IMAGES'].value: + if not current_app.config['USE_REMOTE_IMAGES']: return RebrickableImage.static_url( self.fields.set_num, 'SETS_FOLDER' @@ -182,9 +182,9 @@ class BrickSet(BrickRecord): # Compute the url for the rebrickable page def url_for_rebrickable(self, /) -> str: - if current_app.config['REBRICKABLE_LINKS'].value: + if current_app.config['REBRICKABLE_LINKS']: try: - return current_app.config['REBRICKABLE_LINK_SET_PATTERN'].value.format( # noqa: E501 + return current_app.config['REBRICKABLE_LINK_SET_PATTERN'].format( # noqa: E501 number=self.fields.set_num.lower(), ) except Exception: diff --git a/bricktracker/set_list.py b/bricktracker/set_list.py index 6bbd1bf..41d94a6 100644 --- a/bricktracker/set_list.py +++ b/bricktracker/set_list.py @@ -26,7 +26,7 @@ class BrickSetList(BrickRecordList[BrickSet]): self.themes = [] # Store the order for this list - self.order = current_app.config['SETS_DEFAULT_ORDER'].value + self.order = current_app.config['SETS_DEFAULT_ORDER'] # All the sets def all(self, /) -> Self: @@ -60,7 +60,7 @@ class BrickSetList(BrickRecordList[BrickSet]): # Last added sets def last(self, /, limit: int = 6) -> Self: # Randomize - if current_app.config['RANDOM'].value: + if current_app.config['RANDOM']: order = 'RANDOM()' else: order = 'sets.rowid DESC' diff --git a/bricktracker/socket.py b/bricktracker/socket.py index 44ddadf..8b7a25f 100644 --- a/bricktracker/socket.py +++ b/bricktracker/socket.py @@ -56,19 +56,19 @@ class BrickSocket(object): # Compute the namespace self.namespace = '/{namespace}'.format( - namespace=app.config['SOCKET_NAMESPACE'].value + namespace=app.config['SOCKET_NAMESPACE'] ) # Inject CORS if a domain is defined - if app.config['DOMAIN_NAME'].value != '': - kwargs['cors_allowed_origins'] = app.config['DOMAIN_NAME'].value + if app.config['DOMAIN_NAME'] != '': + kwargs['cors_allowed_origins'] = app.config['DOMAIN_NAME'] # Instantiate the socket self.socket = SocketIO( self.app, *args, **kwargs, - path=app.config['SOCKET_PATH'].value, + path=app.config['SOCKET_PATH'], async_mode='eventlet', ) diff --git a/bricktracker/sql.py b/bricktracker/sql.py index 77aa7b5..f6f478d 100644 --- a/bricktracker/sql.py +++ b/bricktracker/sql.py @@ -37,7 +37,7 @@ class BrickSQL(object): logger.debug('SQLite3: connect') self.connection = sqlite3.connect( - current_app.config['DATABASE_PATH'].value + current_app.config['DATABASE_PATH'] ) # Setup the row factory to get pseudo-dicts rather than tuples @@ -249,7 +249,7 @@ class BrickSQL(object): # Delete the database @staticmethod def delete() -> None: - os.remove(current_app.config['DATABASE_PATH'].value) + os.remove(current_app.config['DATABASE_PATH']) # Info logger.info('The database has been deleted') @@ -292,7 +292,7 @@ class BrickSQL(object): # Replace the database with a new file @staticmethod def upload(file: FileStorage, /) -> None: - file.save(current_app.config['DATABASE_PATH'].value) + file.save(current_app.config['DATABASE_PATH']) # Info logger.info('The database has been imported using file {file}'.format( diff --git a/bricktracker/theme_list.py b/bricktracker/theme_list.py index d8a0b47..160530c 100644 --- a/bricktracker/theme_list.py +++ b/bricktracker/theme_list.py @@ -33,7 +33,7 @@ class BrickThemeList(object): # Try to read the themes from a CSV file try: - with open(current_app.config['THEMES_PATH'].value, newline='') as themes_file: # noqa: E501 + with open(current_app.config['THEMES_PATH'], newline='') as themes_file: # noqa: E501 themes_reader = csv.reader(themes_file) # Ignore the header @@ -44,7 +44,7 @@ class BrickThemeList(object): BrickThemeList.themes[theme.id] = theme # File stats - stat = os.stat(current_app.config['THEMES_PATH'].value) + stat = os.stat(current_app.config['THEMES_PATH']) BrickThemeList.size = stat.st_size BrickThemeList.mtime = datetime.fromtimestamp(stat.st_mtime, tz=timezone.utc) # noqa: E501 @@ -78,7 +78,7 @@ class BrickThemeList(object): def human_time(self) -> str: if self.mtime is not None: return self.mtime.astimezone(g.timezone).strftime( - current_app.config['FILE_DATETIME_FORMAT'].value + current_app.config['FILE_DATETIME_FORMAT'] ) else: return '' @@ -87,7 +87,7 @@ class BrickThemeList(object): @staticmethod def update() -> None: response = requests.get( - current_app.config['THEMES_FILE_URL'].value, + current_app.config['THEMES_FILE_URL'], stream=True, ) @@ -98,7 +98,7 @@ class BrickThemeList(object): content = gzip.GzipFile(fileobj=response.raw) - with open(current_app.config['THEMES_PATH'].value, 'wb') as f: + with open(current_app.config['THEMES_PATH'], 'wb') as f: copyfileobj(content, f) logger.info('Theme list updated') diff --git a/bricktracker/views/add.py b/bricktracker/views/add.py index 45b08a3..218a0bf 100644 --- a/bricktracker/views/add.py +++ b/bricktracker/views/add.py @@ -17,8 +17,8 @@ def add() -> str: return render_template( 'add.html', - path=current_app.config['SOCKET_PATH'].value, - namespace=current_app.config['SOCKET_NAMESPACE'].value, + path=current_app.config['SOCKET_PATH'], + namespace=current_app.config['SOCKET_NAMESPACE'], messages=MESSAGES ) @@ -32,7 +32,7 @@ def bulk() -> str: return render_template( 'bulk.html', - path=current_app.config['SOCKET_PATH'].value, - namespace=current_app.config['SOCKET_NAMESPACE'].value, + path=current_app.config['SOCKET_PATH'], + namespace=current_app.config['SOCKET_NAMESPACE'], messages=MESSAGES ) diff --git a/bricktracker/views/admin.py b/bricktracker/views/admin.py index 77b8c63..5dcb157 100644 --- a/bricktracker/views/admin.py +++ b/bricktracker/views/admin.py @@ -160,19 +160,19 @@ def do_delete_database() -> Response: def download_database() -> Response: # Create a file name with a timestamp embedded name, extension = os.path.splitext( - os.path.basename(current_app.config['DATABASE_PATH'].value) + os.path.basename(current_app.config['DATABASE_PATH']) ) # Info logger.info('The database has been downloaded') return send_file( - current_app.config['DATABASE_PATH'].value, + current_app.config['DATABASE_PATH'], as_attachment=True, download_name='{name}-{timestamp}{extension}'.format( name=name, timestamp=datetime.now().astimezone(g.timezone).strftime( - current_app.config['DATABASE_TIMESTAMP_FORMAT'].value + current_app.config['DATABASE_TIMESTAMP_FORMAT'] ), extension=extension ) diff --git a/bricktracker/views/instructions.py b/bricktracker/views/instructions.py index 6145914..047e961 100644 --- a/bricktracker/views/instructions.py +++ b/bricktracker/views/instructions.py @@ -114,7 +114,7 @@ def do_upload() -> Response: file = upload_helper( 'file', 'instructions.upload', - extensions=current_app.config['INSTRUCTIONS_ALLOWED_EXTENSIONS'].value, + extensions=current_app.config['INSTRUCTIONS_ALLOWED_EXTENSIONS'], ) if isinstance(file, Response): diff --git a/bricktracker/wish_list.py b/bricktracker/wish_list.py index 266ee43..8c55408 100644 --- a/bricktracker/wish_list.py +++ b/bricktracker/wish_list.py @@ -18,7 +18,7 @@ class BrickWishList(BrickRecordList[BrickWish]): def all(self, /) -> Self: # Load the wished sets from the database for record in self.select( - order=current_app.config['WISHES_DEFAULT_ORDER'].value + order=current_app.config['WISHES_DEFAULT_ORDER'] ): brickwish = BrickWish(record=record) diff --git a/templates/add.html b/templates/add.html index a4a4917..1251525 100644 --- a/templates/add.html +++ b/templates/add.html @@ -4,7 +4,7 @@ {% block main %}
- {% if not config['HIDE_ADD_BULK_SET'].value %} + {% if not config['HIDE_ADD_BULK_SET'] %} {% if all %} - {{ table.dynamic('minifigures', no_sort='0', number='2, 3, 4')}} + {% endif %} diff --git a/templates/minifigures.html b/templates/minifigures.html index 5b42357..b28b600 100644 --- a/templates/minifigures.html +++ b/templates/minifigures.html @@ -8,4 +8,8 @@ {% include 'minifigure/table.html' %} {% endwith %}
+{% endblock %} + +{% block scripts %} + {% endblock %} \ No newline at end of file diff --git a/templates/missing.html b/templates/missing.html index 659205b..de36f35 100644 --- a/templates/missing.html +++ b/templates/missing.html @@ -8,4 +8,8 @@ {% include 'part/table.html' %} {% endwith %} +{% endblock %} + +{% block scripts %} + {% endblock %} \ No newline at end of file diff --git a/templates/part/table.html b/templates/part/table.html index c47ca71..3603392 100644 --- a/templates/part/table.html +++ b/templates/part/table.html @@ -52,5 +52,9 @@ {% if all %} - {{ table.dynamic('parts', no_sort='0', number='3, 4, 5, 6')}} + {% endif %} \ No newline at end of file diff --git a/templates/parts.html b/templates/parts.html index 9ed411b..d2cb4b3 100644 --- a/templates/parts.html +++ b/templates/parts.html @@ -8,4 +8,8 @@ {% include 'part/table.html' %} {% endwith %} +{% endblock %} + +{% block scripts %} + {% endblock %} \ No newline at end of file diff --git a/templates/wish/table.html b/templates/wish/table.html index 03d7102..83536e3 100644 --- a/templates/wish/table.html +++ b/templates/wish/table.html @@ -41,5 +41,9 @@ {% if all %} - {{ table.dynamic('wish', no_sort='0,7')}} + {% endif %} diff --git a/templates/wishes.html b/templates/wishes.html index 1593a24..cc9a242 100644 --- a/templates/wishes.html +++ b/templates/wishes.html @@ -28,4 +28,8 @@ {% include 'wish/table.html' %} {% endwith %} +{% endblock %} + +{% block scripts %} + {% endblock %} \ No newline at end of file From 982a1fa8dbd0d11f00c6eaec9a8f9dced5773e71 Mon Sep 17 00:00:00 2001 From: Gregoo Date: Fri, 24 Jan 2025 15:55:15 +0100 Subject: [PATCH 46/50] Simplify the way javascript is loaded (we don't have that many scripts running) and use data attribute to instantiate grid and tables --- static/scripts/changer.js | 18 ++++++++---------- static/scripts/grid.js | 19 ++++++++++++------- static/scripts/table.js | 24 +++++++++++++++++++++--- templates/add.html | 4 ---- templates/admin.html | 4 ---- templates/admin/checkbox.html | 5 ----- templates/base.html | 16 +++++++++++++++- templates/bulk.html | 4 ---- templates/delete.html | 9 --------- templates/instructions.html | 4 ---- templates/instructions/delete.html | 5 ----- templates/instructions/rename.html | 5 ----- templates/instructions/table.html | 13 +++---------- templates/macro/table.html | 12 ++++++------ templates/minifigure.html | 5 ----- templates/minifigure/table.html | 9 +-------- templates/minifigures.html | 4 ---- templates/missing.html | 4 ---- templates/part.html | 5 ----- templates/part/table.html | 9 +-------- templates/parts.html | 4 ---- templates/set.html | 11 ----------- templates/sets.html | 13 +------------ templates/wish/table.html | 13 +++---------- templates/wishes.html | 4 ---- 25 files changed, 71 insertions(+), 152 deletions(-) diff --git a/static/scripts/changer.js b/static/scripts/changer.js index 31177d5..224e24b 100644 --- a/static/scripts/changer.js +++ b/static/scripts/changer.js @@ -115,13 +115,11 @@ class BrickChanger { } // Helper to setup the changer -const setup_changers = () => { - document.querySelectorAll("*[data-changer-id]").forEach(el => { - new BrickChanger( - el.dataset.changerPrefix, - el.dataset.changerId, - el.dataset.changerUrl, - el.dataset.changerParent - ); - }); -} \ No newline at end of file +const setup_changers = () => document.querySelectorAll("*[data-changer-id]").forEach( + el => new BrickChanger( + el.dataset.changerPrefix, + el.dataset.changerId, + el.dataset.changerUrl, + el.dataset.changerParent + ) +); \ No newline at end of file diff --git a/static/scripts/grid.js b/static/scripts/grid.js index 6246817..42b8ac3 100644 --- a/static/scripts/grid.js +++ b/static/scripts/grid.js @@ -50,15 +50,15 @@ class BrickGridSortButton { // Grid class class BrickGrid { - constructor(id) { - this.id = id; + constructor(grid) { + this.id = grid.id; // Grid elements (built based on the initial id) - this.html_grid = document.getElementById(id); - this.html_sort = document.getElementById(`${id}-sort`); - this.html_search = document.getElementById(`${id}-search`); - this.html_filter = document.getElementById(`${id}-filter`); - this.html_theme = document.getElementById(`${id}-theme`); + this.html_grid = document.getElementById(this.id); + this.html_sort = document.getElementById(`${this.id}-sort`); + this.html_search = document.getElementById(`${this.id}-search`); + this.html_filter = document.getElementById(`${this.id}-filter`); + this.html_theme = document.getElementById(`${this.id}-theme`); // Sort buttons this.html_sort_buttons = {}; @@ -251,3 +251,8 @@ class BrickGrid { } } } + +// Helper to setup the grids +const setup_grids = () => document.querySelectorAll('*[data-grid="true"]').forEach( + el => new BrickGrid(el) +); diff --git a/static/scripts/table.js b/static/scripts/table.js index 68179af..669afc5 100644 --- a/static/scripts/table.js +++ b/static/scripts/table.js @@ -1,6 +1,19 @@ class BrickTable { - constructor(id, per_page, no_sort = [], number = []) { - const columns = []; + constructor(table, per_page) { + const columns = [] + const no_sort = []; + const number = []; + + // Read the table header for parameters + table.querySelectorAll('th').forEach((th, index) => { + if (th.dataset.tableNoSort) { + no_sort.push(index); + } + + if (th.dataset.tableNumber) { + number.push(index); + } + }); if (no_sort.length) { columns.push({ select: no_sort, sortable: false, searchable: false }); @@ -10,7 +23,7 @@ class BrickTable { columns.push({ select: number, type: "number", searchable: false }); } - this.table = new simpleDatatables.DataTable(`#${id}`, { + this.table = new simpleDatatables.DataTable(`#${table.id}`, { columns: columns, pagerDelta: 1, perPage: per_page, @@ -67,3 +80,8 @@ class BrickTable { return search; } } + +// Helper to setup the tables +const setup_tables = (per_page) => document.querySelectorAll('table[data-table="true"]').forEach( + el => new BrickTable(el, per_page) +); diff --git a/templates/add.html b/templates/add.html index 8fff618..140eec6 100644 --- a/templates/add.html +++ b/templates/add.html @@ -70,7 +70,3 @@ {% include 'set/socket.html' %} {% endblock %} - -{% block scripts %} - -{% endblock %} \ No newline at end of file diff --git a/templates/admin.html b/templates/admin.html index 860d99f..dc87256 100644 --- a/templates/admin.html +++ b/templates/admin.html @@ -42,7 +42,3 @@ {% endblock %} - -{% block scripts %} - -{% endblock %} \ No newline at end of file diff --git a/templates/admin/checkbox.html b/templates/admin/checkbox.html index 7cbd59d..b71cc2a 100644 --- a/templates/admin/checkbox.html +++ b/templates/admin/checkbox.html @@ -60,8 +60,3 @@ {{ accordion.footer() }} - diff --git a/templates/base.html b/templates/base.html index 23badab..1132f2b 100644 --- a/templates/base.html +++ b/templates/base.html @@ -78,11 +78,25 @@ + + + + + + + + - {% block scripts %}{% endblock %} \ No newline at end of file diff --git a/templates/bulk.html b/templates/bulk.html index 1974e1b..6e6e5d8 100644 --- a/templates/bulk.html +++ b/templates/bulk.html @@ -62,7 +62,3 @@ {% include 'set/socket.html' %} {% endwith %} {% endblock %} - -{% block scripts %} - -{% endblock %} \ No newline at end of file diff --git a/templates/delete.html b/templates/delete.html index d61821a..07c2b08 100644 --- a/templates/delete.html +++ b/templates/delete.html @@ -12,13 +12,4 @@ - {% endblock %} - -{% block scripts %} - -{% endblock %} \ No newline at end of file diff --git a/templates/instructions.html b/templates/instructions.html index 7959796..70cd1eb 100644 --- a/templates/instructions.html +++ b/templates/instructions.html @@ -23,7 +23,3 @@ {% endif %} {% endblock %} - -{% block scripts %} - -{% endblock %} \ No newline at end of file diff --git a/templates/instructions/delete.html b/templates/instructions/delete.html index 9443dd0..247e703 100644 --- a/templates/instructions/delete.html +++ b/templates/instructions/delete.html @@ -28,8 +28,3 @@ - diff --git a/templates/instructions/rename.html b/templates/instructions/rename.html index 0be3dbe..a3269c1 100644 --- a/templates/instructions/rename.html +++ b/templates/instructions/rename.html @@ -34,8 +34,3 @@ - diff --git a/templates/instructions/table.html b/templates/instructions/table.html index 8f507aa..8ad9f3c 100644 --- a/templates/instructions/table.html +++ b/templates/instructions/table.html @@ -1,14 +1,14 @@ {% import 'macro/table.html' as table %}
- +
- + {% if g.login.is_authenticated() %} - + {% endif %} @@ -46,10 +46,3 @@
Filename Set Image Image Actions Actions
-{% if all %} - -{% endif %} diff --git a/templates/macro/table.html b/templates/macro/table.html index d4246f2..91bb1c9 100644 --- a/templates/macro/table.html +++ b/templates/macro/table.html @@ -1,25 +1,25 @@ {% macro header(color=false, quantity=false, missing=false, missing_parts=false, sets=false, minifigures=false) %} - Image + Image Name {% if color %} Color {% endif %} {% if quantity %} - Quantity + Quantity {% endif %} {% if missing %} - Missing + Missing {% endif %} {% if missing_parts %} - Missing parts + Missing parts {% endif %} {% if sets %} - Sets + Sets {% endif %} {% if minifigures %} - Minifigures + Minifigures {% endif %} diff --git a/templates/minifigure.html b/templates/minifigure.html index 3359897..52ed7a1 100644 --- a/templates/minifigure.html +++ b/templates/minifigure.html @@ -12,9 +12,4 @@ - {% endblock %} diff --git a/templates/minifigure/table.html b/templates/minifigure/table.html index 6315fec..94ccef7 100644 --- a/templates/minifigure/table.html +++ b/templates/minifigure/table.html @@ -1,7 +1,7 @@ {% import 'macro/table.html' as table %}
- +
{{ table.header(quantity=true, missing_parts=true, sets=true) }} {% for item in table_collection %} @@ -21,10 +21,3 @@
-{% if all %} - -{% endif %} diff --git a/templates/minifigures.html b/templates/minifigures.html index b28b600..ce42f18 100644 --- a/templates/minifigures.html +++ b/templates/minifigures.html @@ -9,7 +9,3 @@ {% endwith %} {% endblock %} - -{% block scripts %} - -{% endblock %} \ No newline at end of file diff --git a/templates/missing.html b/templates/missing.html index de36f35..d7c82a5 100644 --- a/templates/missing.html +++ b/templates/missing.html @@ -9,7 +9,3 @@ {% endwith %} {% endblock %} - -{% block scripts %} - -{% endblock %} \ No newline at end of file diff --git a/templates/part.html b/templates/part.html index 969b676..40899d9 100644 --- a/templates/part.html +++ b/templates/part.html @@ -12,9 +12,4 @@ - {% endblock %} diff --git a/templates/part/table.html b/templates/part/table.html index 3603392..1fca264 100644 --- a/templates/part/table.html +++ b/templates/part/table.html @@ -1,7 +1,7 @@ {% import 'macro/table.html' as table %}
- +
{{ table.header(color=true, quantity=not no_quantity, missing=not no_missing, sets=all, minifigures=all) }} {% for item in table_collection %} @@ -51,10 +51,3 @@
-{% if all %} - -{% endif %} \ No newline at end of file diff --git a/templates/parts.html b/templates/parts.html index d2cb4b3..1c3e417 100644 --- a/templates/parts.html +++ b/templates/parts.html @@ -9,7 +9,3 @@ {% endwith %} {% endblock %} - -{% block scripts %} - -{% endblock %} \ No newline at end of file diff --git a/templates/set.html b/templates/set.html index d5eeef9..69ba6dd 100644 --- a/templates/set.html +++ b/templates/set.html @@ -12,15 +12,4 @@ - {% endblock %} - -{% block scripts %} - - -{% endblock %} \ No newline at end of file diff --git a/templates/sets.html b/templates/sets.html index 2517c96..ea08c2d 100644 --- a/templates/sets.html +++ b/templates/sets.html @@ -58,7 +58,7 @@ -
+
{% for item in collection %}
{% with index=loop.index0 %} @@ -67,19 +67,8 @@
{% endfor %}
-
{% else %} {% include 'set/empty.html' %} {% endif %} {% endblock %} - -{% block scripts %} - - -{% endblock %} \ No newline at end of file diff --git a/templates/wish/table.html b/templates/wish/table.html index 83536e3..73a5eb8 100644 --- a/templates/wish/table.html +++ b/templates/wish/table.html @@ -2,10 +2,10 @@ {% import 'macro/badge.html' as badge %}
- +
- + @@ -13,7 +13,7 @@ {% if g.login.is_authenticated() %} - + {% endif %} @@ -40,10 +40,3 @@
Image Image Set Name Theme Parts Retirement Actions Actions
-{% if all %} - -{% endif %} diff --git a/templates/wishes.html b/templates/wishes.html index cc9a242..eac8255 100644 --- a/templates/wishes.html +++ b/templates/wishes.html @@ -29,7 +29,3 @@ {% endwith %} {% endblock %} - -{% block scripts %} - -{% endblock %} \ No newline at end of file From 3d878ea7c5451602a3602bf77ff98001b25637ad Mon Sep 17 00:00:00 2001 From: Gregoo Date: Fri, 24 Jan 2025 15:59:18 +0100 Subject: [PATCH 47/50] Add a changelog --- CHANGELOG.md | 196 +++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 196 insertions(+) create mode 100644 CHANGELOG.md diff --git a/CHANGELOG.md b/CHANGELOG.md new file mode 100644 index 0000000..571c522 --- /dev/null +++ b/CHANGELOG.md @@ -0,0 +1,196 @@ +# Changelog + +## 1.1.0: Deduped sets, custom checkboxes and database upgrade + +### Database + +- Sets + - Deduplicating rebrickable sets (unique) and bricktracker sets (can be n bricktracker sets for one rebrickable set) + +### Docs + +- Removed extra `
` to accomodate Gitea Markdown +- Add an organized DOCS.md documentation page +- Database upgrade/migration +- Checkboxes + +### Code + +- Admin + - Split the views before admin because an unmanageable monster view + +- Checkboxes + - Customizable checkboxes for set (amount and names, displayed on the grid or not) + - Replaced the 3 original routes to update the status with a generic route to accomodate any custom status + +- Instructions + - Base instructions on RebrickableSet (the generic one) rather than BrickSet (the specific one) + - Refine set number detection in file name by making sure each first items is an integer + +- Python + - Make stricter function definition with no "arg_or_keyword" parameters + +- Records + - Consolidate the select() -> not None or Exception -> ingest() process duplicated in every child class + +- SQL + - Forward-only migration mechanism + - Check for database too far in version + - Inject the database version in the file when downloading it + - Quote all indentifiers as best practice + - Allow insert query to be overriden + - Allow insert query to force not being deferred even if not committed + - Allow select query to push context in BrickRecord and BrickRecordList + - Make SQL record counters failsafe as they are used in the admin and it should always work + - Remove BrickSQL.initialize() as it is replaced by upgrade() + +- Sets + - Now that it is deduplicated, adding the same set more than once will not pull it fully from the Rebrickable API (minifigures and parts) + - Make RebrickableSet extend BrickRecord since it is now an item in database + - Make BrickSet extend RebrickableSet now that RebrickableSet is a proper database item + +### UI + +- Checkboxes + - Possibility to hide the checkbox in the grid ("Sets") but sill have all them in the set details + - Management + +- Database + - Migration tool + +- Javascript + - Generic BrickChanger class to handle quick modification through a JSON request with a visual feedback indicator + - Simplify the way javascript scripts are loaded and instantiated + +- Set grid + - Filter by checkboxes and NOT checkboxes + +- Tables + - Fix table search looking inside links pills + +- Wishlist + - Add Rebrickable link badge for sets (@matthew) + +## 1.0.0: New Year revamp + +### Code + +- Authentication + - Basic authentication mechanism with ONE password to protect admin and writes +- CSV + - Remove dependencies to numpy and panda for simpler built-in csv +- Code + - Refactored the Python code + - Modularity (more functions, splitting files) + - Type hinting whenever possible + - Flake8 linter + - Retained most of the original behaviour (with its quirks) +- Colors + - Remove dependency on color.csv +- Configuration + - Moved all the hard-coded parameters into configuration variables + - Most of the variables are configuration through environment variables + - Force instruction, sets, etc path to be relative to static +- Docker + - Added an entrypoint to grab PORT / HOST from the environment if set + - Remove the need to seed the container with files (*.csv, nil files) +- Flask + - Fix improper socketio.run(app.run()) call which lead to hard crash on ^C + - Make use of url_for to create URLs + - Use blueprints to implement routes + - Move views into their own files + - Split GET and POST methods into two different routes for clarity +- Images + - Add an option to use remote images from the Rebrickable CDN rather than downloading everything locally + - Handle nil.png and nil_mf.jpg as true images in /static/sets/ so that they are downloaded whenever necessary when importing a se with missing images +- Instructions + - Scan the files once for the whole app, and re-use the data + - Refresh the instructions from the admin + - More lenient set number detection + - Update when uploading a new one + - Basic file management +- Logs + - Added log lines for change actions (add, check, missing, delete) so that the server is not silent when DEBUG=false +- Minifigures + - Added a variable to control default ordering +- Part(s) + - Added a variable to control default ordering of listing +- Retired sets + - Open the themes once for the whole app, and re-use the data + - Do not hard fail if themes.csv is missing, simply display the IDs + - Light management: resync, download +- Set(s) + - Reworked the set checkboxes with a dedicated route per status + - Switch from homemade ID generator to proven UUID4 for sets ID + - Does not interfere with previously created sets + - Do not rely on sets.csv to check if the set exists + - When adding, commit the set to database only once everything has been processed + - Added a bulk add page + - Keep spare parts when importing + - Added a variable to control default ordering of listing +- Socket + - Make use of socket.io rooms to avoid broadcasting messages to all clients +- SQLite + - Do not hard fail if the database is not present or not initialized + - Open the database once for the context, and re-use the connection + - Move queries to .sql files and load them as Jinja templates + - Use named arguments rather than sets for SQLite queries + - Allow execute() to be deferred to the commit() call to avoid locking the database for long period while importing (locked while downloading images) +- Themes + - Open the themes once for the whole app, and re-use the data + - Do not hard fail if themes.csv is missing, simply display the IDs + - Light management: resync, download + +### UI + +- Admin + - Initialize the database from the web interface + - Reset the database + - Delete the database + - Download the database + - Import the database + - Display the configuration variables + - Many things +- Accordions + - Added a flag to make the accordion items independent +- Branding: + - Add a brick as a logo (CC0 image from: https://iconduck.com/icons/71631/brick) +- Global + - Redesign of the whole app + - Sticky menu bar on top of the page + - Execution time and SQL stats for fun +- Libraries + - Switch from Bulma to Bootstrap, arbitrarily :D + - Use of baguettebox for images (https://github.com/feimosi/baguetteBox.js) + - Use of tinysort to sort and filter the grid (https://github.com/Sjeiti/TinySort) + - Use of sortable for set card tables (https://github.com/tofsjonas/sortable) + - Use of simple-datatables for big tables (https://github.com/fiduswriter/simple-datatables) +- Minifigures + - Added a detail view for a minifigure + - Display which sets are using a minifigure + - Display which sets are missing a minifigure +- Parts + - Added a detail view for a part + - Display which sets are using a part + - Display which sets are missing a part +- Templates + - Use a common base template + - Use HTML fragments/macros for repeted or parametrics items + - a 404 page for wrong URLs + - an error page for expected error messages + - an exception page for unexpected error messages +- Set add + - Two-tiered (with override) import where you see what you will import before importing it + - Add a visual indicator that the socket is connected +- Set card + - Badges to display info like theme, year, parts, etc + - Set image on top of the card, filling the space + - Trick to have a blurry background image fill the void in the card + - Save missing parts on input change rather than by clicking + - Visual feedback of success + - Parts and minifigure in accordions + - Instructions file list +- Set grid + - 4-2-1 card distribution depending on screen size + - Display the index with no set added, rather than redirecting + - Keep last sort in a cookie, and trigger it on page load (can be cleared) From 5ebdf89c859002f93ff5e4cb60481e7eda366513 Mon Sep 17 00:00:00 2001 From: Gregoo Date: Fri, 24 Jan 2025 15:59:45 +0100 Subject: [PATCH 48/50] Bump version to 1.1.0 --- bricktracker/version.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bricktracker/version.py b/bricktracker/version.py index f421248..73c525d 100644 --- a/bricktracker/version.py +++ b/bricktracker/version.py @@ -1,4 +1,4 @@ from typing import Final -__version__: Final[str] = '1.0.0' +__version__: Final[str] = '1.1.0' __database_version__: Final[int] = 6 From 2d3e8cdd8baa18e57bd885dc95f8c90379dc4796 Mon Sep 17 00:00:00 2001 From: Gregoo Date: Fri, 24 Jan 2025 16:05:46 +0100 Subject: [PATCH 49/50] Rename the bricktracker doc to overview --- README.md | 2 +- docs/DOCS.md | 2 +- docs/{bricktracker.md => overview.md} | 0 3 files changed, 2 insertions(+), 2 deletions(-) rename docs/{bricktracker.md => overview.md} (100%) diff --git a/README.md b/README.md index 592e39e..291d8b5 100644 --- a/README.md +++ b/README.md @@ -29,4 +29,4 @@ See [first steps](docs/first-steps.md). Most of the pages should be self explanatory to use. However, you can find more specific documentation in the [documentation](docs/DOCS.md). -You can find screenshots of the application in the [bricktracker](docs/bricktracker.md) documentation file. +You can find screenshots of the application in the [overview](docs/overview.md) documentation file. diff --git a/docs/DOCS.md b/docs/DOCS.md index ae04e1c..450ca29 100644 --- a/docs/DOCS.md +++ b/docs/DOCS.md @@ -4,7 +4,7 @@ This page helps you navigate the documentation of BrickTracker. ## Overview -- [Overview](overview.md) (this screenshots of the application are here!) +- [Overview](overview.md) (the screenshots of the application are here!) ## Installation diff --git a/docs/bricktracker.md b/docs/overview.md similarity index 100% rename from docs/bricktracker.md rename to docs/overview.md From 8b1df86f33433385c481c0854c11102017390c62 Mon Sep 17 00:00:00 2001 From: Gregoo Date: Fri, 24 Jan 2025 17:56:21 +0100 Subject: [PATCH 50/50] Update docker image version --- compose.legacy.yml | 2 +- compose.yaml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/compose.legacy.yml b/compose.legacy.yml index adcb044..27ea59e 100644 --- a/compose.legacy.yml +++ b/compose.legacy.yml @@ -2,7 +2,7 @@ services: bricktracker: container_name: BrickTracker restart: unless-stopped - image: gitea.baerentsen.space/frederikbaerentsen/bricktracker:1.0.0 + image: gitea.baerentsen.space/frederikbaerentsen/bricktracker:1.1.0 ports: - "3333:3333" volumes: diff --git a/compose.yaml b/compose.yaml index 180a189..59dac38 100644 --- a/compose.yaml +++ b/compose.yaml @@ -2,7 +2,7 @@ services: bricktracker: container_name: BrickTracker restart: unless-stopped - image: gitea.baerentsen.space/frederikbaerentsen/bricktracker:1.0.0 + image: gitea.baerentsen.space/frederikbaerentsen/bricktracker:1.1.0 ports: - "3333:3333" volumes: