forked from FrederikBaerentsen/BrickTracker
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
This commit is contained in:
parent
3712356caa
commit
e232e2ab7f
12
app.py
12
app.py
@ -20,19 +20,19 @@ setup_app(app)
|
|||||||
# Create the socket
|
# Create the socket
|
||||||
s = BrickSocket(
|
s = BrickSocket(
|
||||||
app,
|
app,
|
||||||
threaded=not app.config['NO_THREADED_SOCKET'].value,
|
threaded=not app.config['NO_THREADED_SOCKET'],
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
if __name__ == '__main__':
|
if __name__ == '__main__':
|
||||||
# Run the application
|
# Run the application
|
||||||
logger.info('Starting BrickTracker on {host}:{port}'.format(
|
logger.info('Starting BrickTracker on {host}:{port}'.format(
|
||||||
host=app.config['HOST'].value,
|
host=app.config['HOST'],
|
||||||
port=app.config['PORT'].value,
|
port=app.config['PORT'],
|
||||||
))
|
))
|
||||||
s.socket.run(
|
s.socket.run(
|
||||||
app,
|
app,
|
||||||
host=app.config['HOST'].value,
|
host=app.config['HOST'],
|
||||||
debug=app.config['DEBUG'].value,
|
debug=app.config['DEBUG'],
|
||||||
port=app.config['PORT'].value,
|
port=app.config['PORT'],
|
||||||
)
|
)
|
||||||
|
@ -28,7 +28,7 @@ def setup_app(app: Flask) -> None:
|
|||||||
BrickConfigurationList(app)
|
BrickConfigurationList(app)
|
||||||
|
|
||||||
# Set the logging level
|
# Set the logging level
|
||||||
if app.config['DEBUG'].value:
|
if app.config['DEBUG']:
|
||||||
logging.basicConfig(
|
logging.basicConfig(
|
||||||
stream=sys.stdout,
|
stream=sys.stdout,
|
||||||
level=logging.DEBUG,
|
level=logging.DEBUG,
|
||||||
@ -90,7 +90,7 @@ def setup_app(app: Flask) -> None:
|
|||||||
g.request_time = request_time
|
g.request_time = request_time
|
||||||
|
|
||||||
# Register the timezone
|
# Register the timezone
|
||||||
g.timezone = ZoneInfo(current_app.config['TIMEZONE'].value)
|
g.timezone = ZoneInfo(current_app.config['TIMEZONE'])
|
||||||
|
|
||||||
# Version
|
# Version
|
||||||
g.version = __version__
|
g.version = __version__
|
||||||
|
@ -69,6 +69,7 @@ class BrickConfiguration(object):
|
|||||||
# Remove static prefix
|
# Remove static prefix
|
||||||
value = value.removeprefix('static/')
|
value = value.removeprefix('static/')
|
||||||
|
|
||||||
|
# Type casting
|
||||||
if self.cast is not None:
|
if self.cast is not None:
|
||||||
self.value = self.cast(value)
|
self.value = self.cast(value)
|
||||||
else:
|
else:
|
||||||
|
@ -1,46 +1,60 @@
|
|||||||
|
import logging
|
||||||
from typing import Generator
|
from typing import Generator
|
||||||
|
|
||||||
from flask import current_app, Flask
|
from flask import Flask
|
||||||
|
|
||||||
from .config import CONFIG
|
from .config import CONFIG
|
||||||
from .configuration import BrickConfiguration
|
from .configuration import BrickConfiguration
|
||||||
from .exceptions import ConfigurationMissingException
|
from .exceptions import ConfigurationMissingException
|
||||||
|
|
||||||
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
# Application configuration
|
# Application configuration
|
||||||
class BrickConfigurationList(object):
|
class BrickConfigurationList(object):
|
||||||
app: Flask
|
app: Flask
|
||||||
|
configurations: dict[str, BrickConfiguration]
|
||||||
|
|
||||||
# Load configuration
|
# Load configuration
|
||||||
def __init__(self, app: Flask, /):
|
def __init__(self, app: Flask, /):
|
||||||
self.app = app
|
self.app = app
|
||||||
|
|
||||||
|
# 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
|
# Process all configuration items
|
||||||
for config in CONFIG:
|
for config in CONFIG:
|
||||||
item = BrickConfiguration(**config)
|
item = BrickConfiguration(**config)
|
||||||
self.app.config[item.name] = item
|
|
||||||
|
# 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
|
# Check whether a str configuration is set
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def error_unless_is_set(name: str):
|
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(
|
raise ConfigurationMissingException(
|
||||||
'{name} must be defined (using the {environ} environment variable)'.format( # noqa: E501
|
'{name} must be defined (using the {environ} environment variable)'.format( # noqa: E501
|
||||||
name=config.name,
|
name=name,
|
||||||
environ=config.env_name
|
environ=configuration.env_name
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
|
|
||||||
# Get all the configuration items from the app config
|
# Get all the configuration items from the app config
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def list() -> Generator[BrickConfiguration, None, None]:
|
def list() -> Generator[BrickConfiguration, None, None]:
|
||||||
keys = list(current_app.config.keys())
|
keys = sorted(BrickConfigurationList.configurations.keys())
|
||||||
keys.sort()
|
|
||||||
|
|
||||||
for name in keys:
|
for name in keys:
|
||||||
config = current_app.config[name]
|
yield BrickConfigurationList.configurations[name]
|
||||||
|
|
||||||
if isinstance(config, BrickConfiguration):
|
|
||||||
yield config
|
|
||||||
|
@ -39,7 +39,7 @@ class BrickInstructions(object):
|
|||||||
# Store the name and extension, check if extension is allowed
|
# Store the name and extension, check if extension is allowed
|
||||||
self.name, self.extension = os.path.splitext(self.filename)
|
self.name, self.extension = os.path.splitext(self.filename)
|
||||||
self.extension = self.extension.lower()
|
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
|
# Placeholder
|
||||||
self.brickset = None
|
self.brickset = None
|
||||||
@ -67,7 +67,7 @@ class BrickInstructions(object):
|
|||||||
# Display the time in a human format
|
# Display the time in a human format
|
||||||
def human_time(self) -> str:
|
def human_time(self) -> str:
|
||||||
return self.mtime.astimezone(g.timezone).strftime(
|
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
|
# Compute the path of an instruction file
|
||||||
@ -77,7 +77,7 @@ class BrickInstructions(object):
|
|||||||
|
|
||||||
return os.path.join(
|
return os.path.join(
|
||||||
current_app.static_folder, # type: ignore
|
current_app.static_folder, # type: ignore
|
||||||
current_app.config['INSTRUCTIONS_FOLDER'].value,
|
current_app.config['INSTRUCTIONS_FOLDER'],
|
||||||
filename
|
filename
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -118,7 +118,7 @@ class BrickInstructions(object):
|
|||||||
if not self.allowed:
|
if not self.allowed:
|
||||||
return ''
|
return ''
|
||||||
|
|
||||||
folder: str = current_app.config['INSTRUCTIONS_FOLDER'].value
|
folder: str = current_app.config['INSTRUCTIONS_FOLDER']
|
||||||
|
|
||||||
# Compute the path
|
# Compute the path
|
||||||
path = os.path.join(folder, self.filename)
|
path = os.path.join(folder, self.filename)
|
||||||
|
@ -36,7 +36,7 @@ class BrickInstructionsList(object):
|
|||||||
# Make a folder relative to static
|
# Make a folder relative to static
|
||||||
folder: str = os.path.join(
|
folder: str = os.path.join(
|
||||||
current_app.static_folder, # type: ignore
|
current_app.static_folder, # type: ignore
|
||||||
current_app.config['INSTRUCTIONS_FOLDER'].value,
|
current_app.config['INSTRUCTIONS_FOLDER'],
|
||||||
)
|
)
|
||||||
|
|
||||||
for file in os.scandir(folder):
|
for file in os.scandir(folder):
|
||||||
@ -68,9 +68,8 @@ class BrickInstructionsList(object):
|
|||||||
for brickset in BrickSetList().generic().records:
|
for brickset in BrickSetList().generic().records:
|
||||||
bricksets[brickset.fields.set_num] = brickset
|
bricksets[brickset.fields.set_num] = brickset
|
||||||
|
|
||||||
# Return the files
|
|
||||||
for instruction in self.all.values():
|
|
||||||
# Inject the brickset if it exists
|
# Inject the brickset if it exists
|
||||||
|
for instruction in self.all.values():
|
||||||
if (
|
if (
|
||||||
instruction.allowed and
|
instruction.allowed and
|
||||||
instruction.number is not None and
|
instruction.number is not None and
|
||||||
|
@ -12,7 +12,7 @@ class LoginManager(object):
|
|||||||
|
|
||||||
def __init__(self, app: Flask, /):
|
def __init__(self, app: Flask, /):
|
||||||
# Setup basic authentication
|
# Setup basic authentication
|
||||||
app.secret_key = app.config['AUTHENTICATION_KEY'].value
|
app.secret_key = app.config['AUTHENTICATION_KEY']
|
||||||
|
|
||||||
manager = login_manager.LoginManager()
|
manager = login_manager.LoginManager()
|
||||||
manager.login_view = 'login.login' # type: ignore
|
manager.login_view = 'login.login' # type: ignore
|
||||||
@ -23,11 +23,11 @@ class LoginManager(object):
|
|||||||
def user_loader(*arg) -> LoginManager.User:
|
def user_loader(*arg) -> LoginManager.User:
|
||||||
return self.User(
|
return self.User(
|
||||||
'admin',
|
'admin',
|
||||||
app.config['AUTHENTICATION_PASSWORD'].value
|
app.config['AUTHENTICATION_PASSWORD']
|
||||||
)
|
)
|
||||||
|
|
||||||
# If the password is unset, globally disable
|
# 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:
|
# Tells whether the user is authenticated, meaning:
|
||||||
# - Authentication disabled
|
# - Authentication disabled
|
||||||
|
@ -119,7 +119,7 @@ class BrickMinifigure(BrickRecord):
|
|||||||
|
|
||||||
# Compute the url for minifigure part image
|
# Compute the url for minifigure part image
|
||||||
def url_for_image(self, /) -> str:
|
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:
|
if self.fields.set_img_url is None:
|
||||||
file = RebrickableImage.nil_minifigure_name()
|
file = RebrickableImage.nil_minifigure_name()
|
||||||
else:
|
else:
|
||||||
@ -128,15 +128,15 @@ class BrickMinifigure(BrickRecord):
|
|||||||
return RebrickableImage.static_url(file, 'MINIFIGURES_FOLDER')
|
return RebrickableImage.static_url(file, 'MINIFIGURES_FOLDER')
|
||||||
else:
|
else:
|
||||||
if self.fields.set_img_url is None:
|
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:
|
else:
|
||||||
return self.fields.set_img_url
|
return self.fields.set_img_url
|
||||||
|
|
||||||
# Compute the url for the rebrickable page
|
# Compute the url for the rebrickable page
|
||||||
def url_for_rebrickable(self, /) -> str:
|
def url_for_rebrickable(self, /) -> str:
|
||||||
if current_app.config['REBRICKABLE_LINKS'].value:
|
if current_app.config['REBRICKABLE_LINKS']:
|
||||||
try:
|
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(),
|
number=self.fields.fig_num.lower(),
|
||||||
)
|
)
|
||||||
except Exception:
|
except Exception:
|
||||||
|
@ -27,7 +27,7 @@ class BrickMinifigureList(BrickRecordList[BrickMinifigure]):
|
|||||||
self.brickset = None
|
self.brickset = None
|
||||||
|
|
||||||
# Store the order for this list
|
# 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
|
# Load all minifigures
|
||||||
def all(self, /) -> Self:
|
def all(self, /) -> Self:
|
||||||
@ -44,7 +44,7 @@ class BrickMinifigureList(BrickRecordList[BrickMinifigure]):
|
|||||||
# Last added minifigure
|
# Last added minifigure
|
||||||
def last(self, /, limit: int = 6) -> Self:
|
def last(self, /, limit: int = 6) -> Self:
|
||||||
# Randomize
|
# Randomize
|
||||||
if current_app.config['RANDOM'].value:
|
if current_app.config['RANDOM']:
|
||||||
order = 'RANDOM()'
|
order = 'RANDOM()'
|
||||||
else:
|
else:
|
||||||
order = 'minifigures.rowid DESC'
|
order = 'minifigures.rowid DESC'
|
||||||
|
@ -190,9 +190,9 @@ class BrickPart(BrickRecord):
|
|||||||
|
|
||||||
# Compute the url for the bricklink page
|
# Compute the url for the bricklink page
|
||||||
def url_for_bricklink(self, /) -> str:
|
def url_for_bricklink(self, /) -> str:
|
||||||
if current_app.config['BRICKLINK_LINKS'].value:
|
if current_app.config['BRICKLINK_LINKS']:
|
||||||
try:
|
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,
|
number=self.fields.part_num,
|
||||||
)
|
)
|
||||||
except Exception:
|
except Exception:
|
||||||
@ -202,7 +202,7 @@ class BrickPart(BrickRecord):
|
|||||||
|
|
||||||
# Compute the url for the part image
|
# Compute the url for the part image
|
||||||
def url_for_image(self, /) -> str:
|
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:
|
if self.fields.part_img_url is None:
|
||||||
file = RebrickableImage.nil_name()
|
file = RebrickableImage.nil_name()
|
||||||
else:
|
else:
|
||||||
@ -211,7 +211,7 @@ class BrickPart(BrickRecord):
|
|||||||
return RebrickableImage.static_url(file, 'PARTS_FOLDER')
|
return RebrickableImage.static_url(file, 'PARTS_FOLDER')
|
||||||
else:
|
else:
|
||||||
if self.fields.part_img_url is None:
|
if self.fields.part_img_url is None:
|
||||||
return current_app.config['REBRICKABLE_IMAGE_NIL'].value
|
return current_app.config['REBRICKABLE_IMAGE_NIL']
|
||||||
else:
|
else:
|
||||||
return self.fields.part_img_url
|
return self.fields.part_img_url
|
||||||
|
|
||||||
@ -234,9 +234,9 @@ class BrickPart(BrickRecord):
|
|||||||
|
|
||||||
# Compute the url for the rebrickable page
|
# Compute the url for the rebrickable page
|
||||||
def url_for_rebrickable(self, /) -> str:
|
def url_for_rebrickable(self, /) -> str:
|
||||||
if current_app.config['REBRICKABLE_LINKS'].value:
|
if current_app.config['REBRICKABLE_LINKS']:
|
||||||
try:
|
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,
|
number=self.fields.part_num,
|
||||||
color=self.fields.color_id,
|
color=self.fields.color_id,
|
||||||
)
|
)
|
||||||
|
@ -30,7 +30,7 @@ class BrickPartList(BrickRecordList[BrickPart]):
|
|||||||
self.minifigure = None
|
self.minifigure = None
|
||||||
|
|
||||||
# Store the order for this list
|
# 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
|
# Load all parts
|
||||||
def all(self, /) -> Self:
|
def all(self, /) -> Self:
|
||||||
@ -63,10 +63,7 @@ class BrickPartList(BrickRecordList[BrickPart]):
|
|||||||
record=record,
|
record=record,
|
||||||
)
|
)
|
||||||
|
|
||||||
if (
|
if current_app.config['SKIP_SPARE_PARTS'] and part.fields.is_spare:
|
||||||
current_app.config['SKIP_SPARE_PARTS'].value and
|
|
||||||
part.fields.is_spare
|
|
||||||
):
|
|
||||||
continue
|
continue
|
||||||
|
|
||||||
self.records.append(part)
|
self.records.append(part)
|
||||||
@ -92,10 +89,7 @@ class BrickPartList(BrickRecordList[BrickPart]):
|
|||||||
record=record,
|
record=record,
|
||||||
)
|
)
|
||||||
|
|
||||||
if (
|
if current_app.config['SKIP_SPARE_PARTS'] and part.fields.is_spare:
|
||||||
current_app.config['SKIP_SPARE_PARTS'].value and
|
|
||||||
part.fields.is_spare
|
|
||||||
):
|
|
||||||
continue
|
continue
|
||||||
|
|
||||||
self.records.append(part)
|
self.records.append(part)
|
||||||
|
@ -77,7 +77,7 @@ class Rebrickable(Generic[T]):
|
|||||||
|
|
||||||
# Bootstrap a first set of parameters
|
# Bootstrap a first set of parameters
|
||||||
parameters: dict[str, Any] | None = {
|
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
|
# Read all pages
|
||||||
@ -115,7 +115,7 @@ class Rebrickable(Generic[T]):
|
|||||||
# Load from the API
|
# Load from the API
|
||||||
def load(self, /, parameters: dict[str, Any] = {}) -> dict[str, Any]:
|
def load(self, /, parameters: dict[str, Any] = {}) -> dict[str, Any]:
|
||||||
# Inject the API key
|
# 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:
|
try:
|
||||||
return json.loads(
|
return json.loads(
|
||||||
|
@ -70,12 +70,12 @@ class RebrickableImage(object):
|
|||||||
# Return the folder depending on the objects provided
|
# Return the folder depending on the objects provided
|
||||||
def folder(self, /) -> str:
|
def folder(self, /) -> str:
|
||||||
if self.part is not None:
|
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:
|
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
|
# Return the id depending on the objects provided
|
||||||
def id(self, /) -> str:
|
def id(self, /) -> str:
|
||||||
@ -105,13 +105,13 @@ class RebrickableImage(object):
|
|||||||
def url(self, /) -> str:
|
def url(self, /) -> str:
|
||||||
if self.part is not None:
|
if self.part is not None:
|
||||||
if self.part.fields.part_img_url is 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:
|
else:
|
||||||
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.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:
|
else:
|
||||||
return self.minifigure.fields.set_img_url
|
return self.minifigure.fields.set_img_url
|
||||||
|
|
||||||
@ -122,7 +122,7 @@ class RebrickableImage(object):
|
|||||||
def nil_name() -> str:
|
def nil_name() -> str:
|
||||||
filename, _ = os.path.splitext(
|
filename, _ = os.path.splitext(
|
||||||
os.path.basename(
|
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:
|
def nil_minifigure_name() -> str:
|
||||||
filename, _ = os.path.splitext(
|
filename, _ = os.path.splitext(
|
||||||
os.path.basename(
|
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
|
# Return the static URL for an image given a name and folder
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def static_url(name: str, folder_name: str) -> str:
|
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
|
# /!\ Everything is saved as .jpg, even if it came from a .png
|
||||||
# not changing this behaviour.
|
# not changing this behaviour.
|
||||||
|
@ -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(
|
RebrickableImage(
|
||||||
self.brickset,
|
self.brickset,
|
||||||
minifigure=minifigure
|
minifigure=minifigure
|
||||||
|
@ -76,7 +76,7 @@ class RebrickableParts(object):
|
|||||||
for index, part in enumerate(inventory):
|
for index, part in enumerate(inventory):
|
||||||
# Skip spare parts
|
# Skip spare parts
|
||||||
if (
|
if (
|
||||||
current_app.config['SKIP_SPARE_PARTS'].value and
|
current_app.config['SKIP_SPARE_PARTS'] and
|
||||||
part.fields.is_spare
|
part.fields.is_spare
|
||||||
):
|
):
|
||||||
continue
|
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(
|
RebrickableImage(
|
||||||
self.brickset,
|
self.brickset,
|
||||||
minifigure=self.minifigure,
|
minifigure=self.minifigure,
|
||||||
|
@ -55,7 +55,7 @@ class RebrickableSet(object):
|
|||||||
# Insert into database
|
# Insert into database
|
||||||
brickset.insert(commit=False)
|
brickset.insert(commit=False)
|
||||||
|
|
||||||
if not current_app.config['USE_REMOTE_IMAGES'].value:
|
if not current_app.config['USE_REMOTE_IMAGES']:
|
||||||
RebrickableImage(brickset).download()
|
RebrickableImage(brickset).download()
|
||||||
|
|
||||||
# Load the inventory
|
# Load the inventory
|
||||||
@ -210,5 +210,5 @@ class RebrickableSet(object):
|
|||||||
# Insert into database
|
# Insert into database
|
||||||
brickwish.insert()
|
brickwish.insert()
|
||||||
|
|
||||||
if not current_app.config['USE_REMOTE_IMAGES'].value:
|
if not current_app.config['USE_REMOTE_IMAGES']:
|
||||||
RebrickableImage(brickwish).download()
|
RebrickableImage(brickwish).download()
|
||||||
|
@ -33,7 +33,7 @@ class BrickRetiredList(object):
|
|||||||
|
|
||||||
# Try to read the themes from a CSV file
|
# Try to read the themes from a CSV file
|
||||||
try:
|
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)
|
themes_reader = csv.reader(themes_file)
|
||||||
|
|
||||||
# Ignore the header
|
# Ignore the header
|
||||||
@ -44,7 +44,7 @@ class BrickRetiredList(object):
|
|||||||
BrickRetiredList.retired[retired.number] = retired
|
BrickRetiredList.retired[retired.number] = retired
|
||||||
|
|
||||||
# File stats
|
# 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.size = stat.st_size
|
||||||
BrickRetiredList.mtime = datetime.fromtimestamp(stat.st_mtime, tz=timezone.utc) # noqa: E501
|
BrickRetiredList.mtime = datetime.fromtimestamp(stat.st_mtime, tz=timezone.utc) # noqa: E501
|
||||||
|
|
||||||
@ -79,7 +79,7 @@ class BrickRetiredList(object):
|
|||||||
def human_time(self) -> str:
|
def human_time(self) -> str:
|
||||||
if self.mtime is not None:
|
if self.mtime is not None:
|
||||||
return self.mtime.astimezone(g.timezone).strftime(
|
return self.mtime.astimezone(g.timezone).strftime(
|
||||||
current_app.config['FILE_DATETIME_FORMAT'].value
|
current_app.config['FILE_DATETIME_FORMAT']
|
||||||
)
|
)
|
||||||
else:
|
else:
|
||||||
return ''
|
return ''
|
||||||
@ -88,7 +88,7 @@ class BrickRetiredList(object):
|
|||||||
@staticmethod
|
@staticmethod
|
||||||
def update() -> None:
|
def update() -> None:
|
||||||
response = requests.get(
|
response = requests.get(
|
||||||
current_app.config['RETIRED_SETS_FILE_URL'].value,
|
current_app.config['RETIRED_SETS_FILE_URL'],
|
||||||
stream=True,
|
stream=True,
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -99,7 +99,7 @@ class BrickRetiredList(object):
|
|||||||
|
|
||||||
content = gzip.GzipFile(fileobj=response.raw)
|
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)
|
copyfileobj(content, f)
|
||||||
|
|
||||||
logger.info('Retired sets list updated')
|
logger.info('Retired sets list updated')
|
||||||
|
@ -157,7 +157,7 @@ class BrickSet(BrickRecord):
|
|||||||
|
|
||||||
# Compute the url for the set image
|
# Compute the url for the set image
|
||||||
def url_for_image(self, /) -> str:
|
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(
|
return RebrickableImage.static_url(
|
||||||
self.fields.set_num,
|
self.fields.set_num,
|
||||||
'SETS_FOLDER'
|
'SETS_FOLDER'
|
||||||
@ -182,9 +182,9 @@ class BrickSet(BrickRecord):
|
|||||||
|
|
||||||
# Compute the url for the rebrickable page
|
# Compute the url for the rebrickable page
|
||||||
def url_for_rebrickable(self, /) -> str:
|
def url_for_rebrickable(self, /) -> str:
|
||||||
if current_app.config['REBRICKABLE_LINKS'].value:
|
if current_app.config['REBRICKABLE_LINKS']:
|
||||||
try:
|
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(),
|
number=self.fields.set_num.lower(),
|
||||||
)
|
)
|
||||||
except Exception:
|
except Exception:
|
||||||
|
@ -26,7 +26,7 @@ class BrickSetList(BrickRecordList[BrickSet]):
|
|||||||
self.themes = []
|
self.themes = []
|
||||||
|
|
||||||
# Store the order for this list
|
# 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
|
# All the sets
|
||||||
def all(self, /) -> Self:
|
def all(self, /) -> Self:
|
||||||
@ -60,7 +60,7 @@ class BrickSetList(BrickRecordList[BrickSet]):
|
|||||||
# Last added sets
|
# Last added sets
|
||||||
def last(self, /, limit: int = 6) -> Self:
|
def last(self, /, limit: int = 6) -> Self:
|
||||||
# Randomize
|
# Randomize
|
||||||
if current_app.config['RANDOM'].value:
|
if current_app.config['RANDOM']:
|
||||||
order = 'RANDOM()'
|
order = 'RANDOM()'
|
||||||
else:
|
else:
|
||||||
order = 'sets.rowid DESC'
|
order = 'sets.rowid DESC'
|
||||||
|
@ -56,19 +56,19 @@ class BrickSocket(object):
|
|||||||
|
|
||||||
# Compute the namespace
|
# Compute the namespace
|
||||||
self.namespace = '/{namespace}'.format(
|
self.namespace = '/{namespace}'.format(
|
||||||
namespace=app.config['SOCKET_NAMESPACE'].value
|
namespace=app.config['SOCKET_NAMESPACE']
|
||||||
)
|
)
|
||||||
|
|
||||||
# Inject CORS if a domain is defined
|
# Inject CORS if a domain is defined
|
||||||
if app.config['DOMAIN_NAME'].value != '':
|
if app.config['DOMAIN_NAME'] != '':
|
||||||
kwargs['cors_allowed_origins'] = app.config['DOMAIN_NAME'].value
|
kwargs['cors_allowed_origins'] = app.config['DOMAIN_NAME']
|
||||||
|
|
||||||
# Instantiate the socket
|
# Instantiate the socket
|
||||||
self.socket = SocketIO(
|
self.socket = SocketIO(
|
||||||
self.app,
|
self.app,
|
||||||
*args,
|
*args,
|
||||||
**kwargs,
|
**kwargs,
|
||||||
path=app.config['SOCKET_PATH'].value,
|
path=app.config['SOCKET_PATH'],
|
||||||
async_mode='eventlet',
|
async_mode='eventlet',
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -37,7 +37,7 @@ class BrickSQL(object):
|
|||||||
|
|
||||||
logger.debug('SQLite3: connect')
|
logger.debug('SQLite3: connect')
|
||||||
self.connection = 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
|
# Setup the row factory to get pseudo-dicts rather than tuples
|
||||||
@ -249,7 +249,7 @@ class BrickSQL(object):
|
|||||||
# Delete the database
|
# Delete the database
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def delete() -> None:
|
def delete() -> None:
|
||||||
os.remove(current_app.config['DATABASE_PATH'].value)
|
os.remove(current_app.config['DATABASE_PATH'])
|
||||||
|
|
||||||
# Info
|
# Info
|
||||||
logger.info('The database has been deleted')
|
logger.info('The database has been deleted')
|
||||||
@ -292,7 +292,7 @@ class BrickSQL(object):
|
|||||||
# Replace the database with a new file
|
# Replace the database with a new file
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def upload(file: FileStorage, /) -> None:
|
def upload(file: FileStorage, /) -> None:
|
||||||
file.save(current_app.config['DATABASE_PATH'].value)
|
file.save(current_app.config['DATABASE_PATH'])
|
||||||
|
|
||||||
# Info
|
# Info
|
||||||
logger.info('The database has been imported using file {file}'.format(
|
logger.info('The database has been imported using file {file}'.format(
|
||||||
|
@ -33,7 +33,7 @@ class BrickThemeList(object):
|
|||||||
|
|
||||||
# Try to read the themes from a CSV file
|
# Try to read the themes from a CSV file
|
||||||
try:
|
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)
|
themes_reader = csv.reader(themes_file)
|
||||||
|
|
||||||
# Ignore the header
|
# Ignore the header
|
||||||
@ -44,7 +44,7 @@ class BrickThemeList(object):
|
|||||||
BrickThemeList.themes[theme.id] = theme
|
BrickThemeList.themes[theme.id] = theme
|
||||||
|
|
||||||
# File stats
|
# 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.size = stat.st_size
|
||||||
BrickThemeList.mtime = datetime.fromtimestamp(stat.st_mtime, tz=timezone.utc) # noqa: E501
|
BrickThemeList.mtime = datetime.fromtimestamp(stat.st_mtime, tz=timezone.utc) # noqa: E501
|
||||||
|
|
||||||
@ -78,7 +78,7 @@ class BrickThemeList(object):
|
|||||||
def human_time(self) -> str:
|
def human_time(self) -> str:
|
||||||
if self.mtime is not None:
|
if self.mtime is not None:
|
||||||
return self.mtime.astimezone(g.timezone).strftime(
|
return self.mtime.astimezone(g.timezone).strftime(
|
||||||
current_app.config['FILE_DATETIME_FORMAT'].value
|
current_app.config['FILE_DATETIME_FORMAT']
|
||||||
)
|
)
|
||||||
else:
|
else:
|
||||||
return ''
|
return ''
|
||||||
@ -87,7 +87,7 @@ class BrickThemeList(object):
|
|||||||
@staticmethod
|
@staticmethod
|
||||||
def update() -> None:
|
def update() -> None:
|
||||||
response = requests.get(
|
response = requests.get(
|
||||||
current_app.config['THEMES_FILE_URL'].value,
|
current_app.config['THEMES_FILE_URL'],
|
||||||
stream=True,
|
stream=True,
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -98,7 +98,7 @@ class BrickThemeList(object):
|
|||||||
|
|
||||||
content = gzip.GzipFile(fileobj=response.raw)
|
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)
|
copyfileobj(content, f)
|
||||||
|
|
||||||
logger.info('Theme list updated')
|
logger.info('Theme list updated')
|
||||||
|
@ -17,8 +17,8 @@ def add() -> str:
|
|||||||
|
|
||||||
return render_template(
|
return render_template(
|
||||||
'add.html',
|
'add.html',
|
||||||
path=current_app.config['SOCKET_PATH'].value,
|
path=current_app.config['SOCKET_PATH'],
|
||||||
namespace=current_app.config['SOCKET_NAMESPACE'].value,
|
namespace=current_app.config['SOCKET_NAMESPACE'],
|
||||||
messages=MESSAGES
|
messages=MESSAGES
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -32,7 +32,7 @@ def bulk() -> str:
|
|||||||
|
|
||||||
return render_template(
|
return render_template(
|
||||||
'bulk.html',
|
'bulk.html',
|
||||||
path=current_app.config['SOCKET_PATH'].value,
|
path=current_app.config['SOCKET_PATH'],
|
||||||
namespace=current_app.config['SOCKET_NAMESPACE'].value,
|
namespace=current_app.config['SOCKET_NAMESPACE'],
|
||||||
messages=MESSAGES
|
messages=MESSAGES
|
||||||
)
|
)
|
||||||
|
@ -160,19 +160,19 @@ def do_delete_database() -> Response:
|
|||||||
def download_database() -> Response:
|
def download_database() -> Response:
|
||||||
# Create a file name with a timestamp embedded
|
# Create a file name with a timestamp embedded
|
||||||
name, extension = os.path.splitext(
|
name, extension = os.path.splitext(
|
||||||
os.path.basename(current_app.config['DATABASE_PATH'].value)
|
os.path.basename(current_app.config['DATABASE_PATH'])
|
||||||
)
|
)
|
||||||
|
|
||||||
# Info
|
# Info
|
||||||
logger.info('The database has been downloaded')
|
logger.info('The database has been downloaded')
|
||||||
|
|
||||||
return send_file(
|
return send_file(
|
||||||
current_app.config['DATABASE_PATH'].value,
|
current_app.config['DATABASE_PATH'],
|
||||||
as_attachment=True,
|
as_attachment=True,
|
||||||
download_name='{name}-{timestamp}{extension}'.format(
|
download_name='{name}-{timestamp}{extension}'.format(
|
||||||
name=name,
|
name=name,
|
||||||
timestamp=datetime.now().astimezone(g.timezone).strftime(
|
timestamp=datetime.now().astimezone(g.timezone).strftime(
|
||||||
current_app.config['DATABASE_TIMESTAMP_FORMAT'].value
|
current_app.config['DATABASE_TIMESTAMP_FORMAT']
|
||||||
),
|
),
|
||||||
extension=extension
|
extension=extension
|
||||||
)
|
)
|
||||||
|
@ -114,7 +114,7 @@ def do_upload() -> Response:
|
|||||||
file = upload_helper(
|
file = upload_helper(
|
||||||
'file',
|
'file',
|
||||||
'instructions.upload',
|
'instructions.upload',
|
||||||
extensions=current_app.config['INSTRUCTIONS_ALLOWED_EXTENSIONS'].value,
|
extensions=current_app.config['INSTRUCTIONS_ALLOWED_EXTENSIONS'],
|
||||||
)
|
)
|
||||||
|
|
||||||
if isinstance(file, Response):
|
if isinstance(file, Response):
|
||||||
|
@ -18,7 +18,7 @@ class BrickWishList(BrickRecordList[BrickWish]):
|
|||||||
def all(self, /) -> Self:
|
def all(self, /) -> Self:
|
||||||
# Load the wished sets from the database
|
# Load the wished sets from the database
|
||||||
for record in self.select(
|
for record in self.select(
|
||||||
order=current_app.config['WISHES_DEFAULT_ORDER'].value
|
order=current_app.config['WISHES_DEFAULT_ORDER']
|
||||||
):
|
):
|
||||||
brickwish = BrickWish(record=record)
|
brickwish = BrickWish(record=record)
|
||||||
|
|
||||||
|
@ -4,7 +4,7 @@
|
|||||||
|
|
||||||
{% block main %}
|
{% block main %}
|
||||||
<div class="container">
|
<div class="container">
|
||||||
{% if not config['HIDE_ADD_BULK_SET'].value %}
|
{% if not config['HIDE_ADD_BULK_SET'] %}
|
||||||
<div class="alert alert-primary" role="alert">
|
<div class="alert alert-primary" role="alert">
|
||||||
<h4 class="alert-heading">Too many to add?</h4>
|
<h4 class="alert-heading">Too many to add?</h4>
|
||||||
<p class="mb-0">You can import multiple sets at once with <a href="{{ url_for('add.bulk') }}" class="btn btn-primary"><i class="ri-function-add-line"></i> Bulk add</a>.</p>
|
<p class="mb-0">You can import multiple sets at once with <a href="{{ url_for('add.bulk') }}" class="btn btn-primary"><i class="ri-function-add-line"></i> Bulk add</a>.</p>
|
||||||
|
@ -21,7 +21,7 @@
|
|||||||
{% else %}
|
{% else %}
|
||||||
{% include 'admin/logout.html' %}
|
{% include 'admin/logout.html' %}
|
||||||
{% include 'admin/instructions.html' %}
|
{% include 'admin/instructions.html' %}
|
||||||
{% if not config['USE_REMOTE_IMAGES'].value %}
|
{% if not config['USE_REMOTE_IMAGES'] %}
|
||||||
{% include 'admin/image.html' %}
|
{% include 'admin/image.html' %}
|
||||||
{% endif %}
|
{% endif %}
|
||||||
{% include 'admin/theme.html' %}
|
{% include 'admin/theme.html' %}
|
||||||
|
@ -6,7 +6,7 @@
|
|||||||
{% if error %}<div class="alert alert-danger" role="alert"><strong>Error:</strong> {{ error }}.</div>{% endif %}
|
{% if error %}<div class="alert alert-danger" role="alert"><strong>Error:</strong> {{ error }}.</div>{% endif %}
|
||||||
{% if not is_init %}
|
{% if not is_init %}
|
||||||
<div class="alert alert-warning" role="alert">
|
<div class="alert alert-warning" role="alert">
|
||||||
<p>The database file is: <code>{{ config['DATABASE_PATH'].value }}</code>. The database is not initialized.</p>
|
<p>The database file is: <code>{{ config['DATABASE_PATH'] }}</code>. The database is not initialized.</p>
|
||||||
<hr>
|
<hr>
|
||||||
<form action="{{ url_for('admin.init_database') }}" method="post" class="text-end">
|
<form action="{{ url_for('admin.init_database') }}" method="post" class="text-end">
|
||||||
<button type="submit" class="btn btn-warning"><i class="ri-reset-right-fill"></i> Initialize the database</button>
|
<button type="submit" class="btn btn-warning"><i class="ri-reset-right-fill"></i> Initialize the database</button>
|
||||||
@ -25,7 +25,7 @@
|
|||||||
</form>
|
</form>
|
||||||
</div>
|
</div>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
<p>The database file is: <code>{{ config['DATABASE_PATH'].value }}</code>. <i class="ri-checkbox-circle-line"></i> The database is initialized.</p>
|
<p>The database file is: <code>{{ config['DATABASE_PATH'] }}</code>. <i class="ri-checkbox-circle-line"></i> The database is initialized.</p>
|
||||||
<p>
|
<p>
|
||||||
<a href="{{ url_for('admin.download_database') }}" class="btn btn-primary" role="button"><i class="ri-download-line"></i> Download the database file</a>
|
<a href="{{ url_for('admin.download_database') }}" class="btn btn-primary" role="button"><i class="ri-download-line"></i> Download the database file</a>
|
||||||
</p>
|
</p>
|
||||||
|
@ -3,8 +3,8 @@
|
|||||||
{{ accordion.header('Instructions', 'instructions', 'admin', expanded=open_instructions, icon='file-line') }}
|
{{ accordion.header('Instructions', 'instructions', 'admin', expanded=open_instructions, icon='file-line') }}
|
||||||
<h5 class="border-bottom">Folder</h5>
|
<h5 class="border-bottom">Folder</h5>
|
||||||
<p>
|
<p>
|
||||||
The instructions files folder is: <code>{{ config['INSTRUCTIONS_FOLDER'].value }}</code>. <br>
|
The instructions files folder is: <code>{{ config['INSTRUCTIONS_FOLDER'] }}</code>. <br>
|
||||||
Allowed file formats for instructions are the following: <code>{{ ', '.join(config['INSTRUCTIONS_ALLOWED_EXTENSIONS'].value) }}</code>.
|
Allowed file formats for instructions are the following: <code>{{ ', '.join(config['INSTRUCTIONS_ALLOWED_EXTENSIONS']) }}</code>.
|
||||||
</p>
|
</p>
|
||||||
<h5 class="border-bottom">Counters</h5>
|
<h5 class="border-bottom">Counters</h5>
|
||||||
<p>
|
<p>
|
||||||
|
@ -4,7 +4,7 @@
|
|||||||
<h5 class="border-bottom">File</h5>
|
<h5 class="border-bottom">File</h5>
|
||||||
{% if retired.exception %}<div class="alert alert-danger" role="alert">An exception occured while loading processing the retired sets: {{ retired.exception }}</div>{% endif %}
|
{% if retired.exception %}<div class="alert alert-danger" role="alert">An exception occured while loading processing the retired sets: {{ retired.exception }}</div>{% endif %}
|
||||||
<p>
|
<p>
|
||||||
The retired sets file is: <code>{{ config['RETIRED_SETS_PATH'].value }}</code>.
|
The retired sets file is: <code>{{ config['RETIRED_SETS_PATH'] }}</code>.
|
||||||
{% if retired.size %}<span class="badge rounded-pill text-bg-info fw-normal"><i class="ri-hard-drive-line"></i> {{ retired.human_size() }}</span>{% endif %}
|
{% if retired.size %}<span class="badge rounded-pill text-bg-info fw-normal"><i class="ri-hard-drive-line"></i> {{ retired.human_size() }}</span>{% endif %}
|
||||||
{% if retired.mtime %}<span class="badge rounded-pill text-bg-light border fw-normal"><i class="ri-calendar-line"></i> {{ retired.human_time() }}</span>{% endif %}
|
{% if retired.mtime %}<span class="badge rounded-pill text-bg-light border fw-normal"><i class="ri-calendar-line"></i> {{ retired.human_time() }}</span>{% endif %}
|
||||||
</p>
|
</p>
|
||||||
|
@ -4,7 +4,7 @@
|
|||||||
<h5 class="border-bottom">File</h5>
|
<h5 class="border-bottom">File</h5>
|
||||||
{% if theme.exception %}<div class="alert alert-danger" role="alert">An exception occured while loading processing the themes: {{ theme.exception }}</div>{% endif %}
|
{% if theme.exception %}<div class="alert alert-danger" role="alert">An exception occured while loading processing the themes: {{ theme.exception }}</div>{% endif %}
|
||||||
<p>
|
<p>
|
||||||
The themes file is: <code>{{ config['THEMES_PATH'].value }}</code>.
|
The themes file is: <code>{{ config['THEMES_PATH'] }}</code>.
|
||||||
{% if theme.size %}<span class="badge rounded-pill text-bg-info fw-normal"><i class="ri-hard-drive-line"></i> {{ theme.human_size() }}</span>{% endif %}
|
{% if theme.size %}<span class="badge rounded-pill text-bg-info fw-normal"><i class="ri-hard-drive-line"></i> {{ theme.human_size() }}</span>{% endif %}
|
||||||
{% if theme.mtime %}<span class="badge rounded-pill text-bg-light border fw-normal"><i class="ri-calendar-line"></i> {{ theme.human_time() }}</span>{% endif %}
|
{% if theme.mtime %}<span class="badge rounded-pill text-bg-light border fw-normal"><i class="ri-calendar-line"></i> {{ theme.human_time() }}</span>{% endif %}
|
||||||
</p>
|
</p>
|
||||||
|
@ -25,7 +25,7 @@
|
|||||||
<div class="collapse navbar-collapse" id="navbarSupportedContent">
|
<div class="collapse navbar-collapse" id="navbarSupportedContent">
|
||||||
<ul class="navbar-nav me-auto mb-2 mb-lg-0">
|
<ul class="navbar-nav me-auto mb-2 mb-lg-0">
|
||||||
{% for item in config['_NAVBAR'] %}
|
{% for item in config['_NAVBAR'] %}
|
||||||
{% if item.flag and not config[item.flag].value %}
|
{% if item.flag and not config[item.flag] %}
|
||||||
<li class="nav-item px-1">
|
<li class="nav-item px-1">
|
||||||
<a {% if request.url_rule.endpoint == item.endpoint %}class="nav-link active" aria-current="page"{% else %}class="nav-link"{% endif %} href="{{ url_for(item.endpoint) }}">
|
<a {% if request.url_rule.endpoint == item.endpoint %}class="nav-link active" aria-current="page"{% else %}class="nav-link"{% endif %} href="{{ url_for(item.endpoint) }}">
|
||||||
{% if item.icon %}
|
{% if item.icon %}
|
||||||
@ -61,7 +61,7 @@
|
|||||||
<div class="col-md-6 d-flex justify-content-center">
|
<div class="col-md-6 d-flex justify-content-center">
|
||||||
<small>
|
<small>
|
||||||
<i class="ri-timer-2-line"></i> {{ g.request_time() }}
|
<i class="ri-timer-2-line"></i> {{ g.request_time() }}
|
||||||
{%if config['DEBUG'].value and g.database_stats %}
|
{%if config['DEBUG'] and g.database_stats %}
|
||||||
| <i class="ri-database-2-line"></i> {{g.database_stats.print() }}
|
| <i class="ri-database-2-line"></i> {{g.database_stats.print() }}
|
||||||
{% endif %}
|
{% endif %}
|
||||||
</small>
|
</small>
|
||||||
|
@ -3,8 +3,8 @@
|
|||||||
{% block main %}
|
{% block main %}
|
||||||
<div class="container-fluid">
|
<div class="container-fluid">
|
||||||
<h2 class="border-bottom lh-base pb-1">
|
<h2 class="border-bottom lh-base pb-1">
|
||||||
<i class="ri-hashtag"></i> {% if config['RANDOM'].value %}Random selection of{% else %}Latest added{% endif %} sets
|
<i class="ri-hashtag"></i> {% if config['RANDOM'] %}Random selection of{% else %}Latest added{% endif %} sets
|
||||||
{% if not config['HIDE_ALL_SETS'].value %}
|
{% if not config['HIDE_ALL_SETS'] %}
|
||||||
<a href="{{ url_for('set.list') }}" class="btn btn-sm btn-primary ms-1">All sets</a>
|
<a href="{{ url_for('set.list') }}" class="btn btn-sm btn-primary ms-1">All sets</a>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
</h2>
|
</h2>
|
||||||
@ -23,8 +23,8 @@
|
|||||||
{% endif %}
|
{% endif %}
|
||||||
{% if minifigure_collection | length %}
|
{% if minifigure_collection | length %}
|
||||||
<h2 class="border-bottom lh-base pb-1">
|
<h2 class="border-bottom lh-base pb-1">
|
||||||
<i class="ri-group-line"></i> {% if config['RANDOM'].value %}Random selection of{% else %}Latest added{% endif %} minifigures
|
<i class="ri-group-line"></i> {% if config['RANDOM'] %}Random selection of{% else %}Latest added{% endif %} minifigures
|
||||||
{% if not config['HIDE_ALL_MINIFIGURES'].value %}
|
{% if not config['HIDE_ALL_MINIFIGURES'] %}
|
||||||
<a href="{{ url_for('minifigure.list') }}" class="btn btn-sm btn-primary ms-1">All minifigures</a>
|
<a href="{{ url_for('minifigure.list') }}" class="btn btn-sm btn-primary ms-1">All minifigures</a>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
</h2>
|
</h2>
|
||||||
|
@ -26,8 +26,8 @@
|
|||||||
<div class="mb-3">
|
<div class="mb-3">
|
||||||
<label for="file" class="form-label">Instructions file</label>
|
<label for="file" class="form-label">Instructions file</label>
|
||||||
<div class="input-group">
|
<div class="input-group">
|
||||||
<input type="file" class="form-control" id="file" name="file" accept="{{ ','.join(config['INSTRUCTIONS_ALLOWED_EXTENSIONS'].value) }}">
|
<input type="file" class="form-control" id="file" name="file" accept="{{ ','.join(config['INSTRUCTIONS_ALLOWED_EXTENSIONS']) }}">
|
||||||
<span class="input-group-text">{{ ', '.join(config['INSTRUCTIONS_ALLOWED_EXTENSIONS'].value) }}</span>
|
<span class="input-group-text">{{ ', '.join(config['INSTRUCTIONS_ALLOWED_EXTENSIONS']) }}</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="text-end">
|
<div class="text-end">
|
||||||
|
@ -16,7 +16,7 @@
|
|||||||
{% endif %}
|
{% endif %}
|
||||||
</button>
|
</button>
|
||||||
</h2>
|
</h2>
|
||||||
<div id="{{ id }}" class="accordion-collapse collapse {% if expanded %}show{% endif %}" {% if not config['INDEPENDENT_ACCORDIONS'].value %}data-bs-parent="#{{ parent }}"{% endif %}>
|
<div id="{{ id }}" class="accordion-collapse collapse {% if expanded %}show{% endif %}" {% if not config['INDEPENDENT_ACCORDIONS'] %}data-bs-parent="#{{ parent }}"{% endif %}>
|
||||||
<div class="accordion-body {% if class %}{{ class }}{% endif %}">
|
<div class="accordion-body {% if class %}{{ class }}{% endif %}">
|
||||||
{% endmacro %}
|
{% endmacro %}
|
||||||
|
|
||||||
|
@ -60,7 +60,7 @@
|
|||||||
{% if number %}{ select: [{{ number }}], type: "number", searchable: false },{% endif %}
|
{% if number %}{ select: [{{ number }}], type: "number", searchable: false },{% endif %}
|
||||||
],
|
],
|
||||||
pagerDelta: 1,
|
pagerDelta: 1,
|
||||||
perPage: {{ config['DEFAULT_TABLE_PER_PAGE'].value }},
|
perPage: {{ config['DEFAULT_TABLE_PER_PAGE'] }},
|
||||||
perPageSelect: [10, 25, 50, 100, 500, 1000],
|
perPageSelect: [10, 25, 50, 100, 500, 1000],
|
||||||
searchable: true,
|
searchable: true,
|
||||||
searchQuerySeparator: "",
|
searchQuerySeparator: "",
|
||||||
|
Loading…
Reference in New Issue
Block a user