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
|
||||
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'],
|
||||
)
|
||||
|
@ -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__
|
||||
|
@ -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:
|
||||
|
@ -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
|
||||
|
||||
# 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)
|
||||
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
|
||||
@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]
|
||||
|
@ -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)
|
||||
|
@ -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
|
||||
for instruction in self.all.values():
|
||||
# Inject the brickset if it exists
|
||||
for instruction in self.all.values():
|
||||
if (
|
||||
instruction.allowed and
|
||||
instruction.number is not None and
|
||||
|
@ -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
|
||||
|
@ -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:
|
||||
|
@ -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'
|
||||
|
@ -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,
|
||||
)
|
||||
|
@ -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)
|
||||
|
@ -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(
|
||||
|
@ -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.
|
||||
|
@ -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
|
||||
|
@ -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,
|
||||
|
@ -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()
|
||||
|
@ -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')
|
||||
|
@ -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:
|
||||
|
@ -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'
|
||||
|
@ -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',
|
||||
)
|
||||
|
||||
|
@ -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(
|
||||
|
@ -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')
|
||||
|
@ -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
|
||||
)
|
||||
|
@ -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
|
||||
)
|
||||
|
@ -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):
|
||||
|
@ -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)
|
||||
|
||||
|
@ -4,7 +4,7 @@
|
||||
|
||||
{% block main %}
|
||||
<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">
|
||||
<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>
|
||||
|
@ -21,7 +21,7 @@
|
||||
{% else %}
|
||||
{% include 'admin/logout.html' %}
|
||||
{% include 'admin/instructions.html' %}
|
||||
{% if not config['USE_REMOTE_IMAGES'].value %}
|
||||
{% if not config['USE_REMOTE_IMAGES'] %}
|
||||
{% include 'admin/image.html' %}
|
||||
{% endif %}
|
||||
{% include 'admin/theme.html' %}
|
||||
|
@ -6,7 +6,7 @@
|
||||
{% if error %}<div class="alert alert-danger" role="alert"><strong>Error:</strong> {{ error }}.</div>{% endif %}
|
||||
{% if not is_init %}
|
||||
<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>
|
||||
<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>
|
||||
@ -25,7 +25,7 @@
|
||||
</form>
|
||||
</div>
|
||||
{% 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>
|
||||
<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>
|
||||
|
@ -3,8 +3,8 @@
|
||||
{{ accordion.header('Instructions', 'instructions', 'admin', expanded=open_instructions, icon='file-line') }}
|
||||
<h5 class="border-bottom">Folder</h5>
|
||||
<p>
|
||||
The instructions files folder is: <code>{{ config['INSTRUCTIONS_FOLDER'].value }}</code>. <br>
|
||||
Allowed file formats for instructions are the following: <code>{{ ', '.join(config['INSTRUCTIONS_ALLOWED_EXTENSIONS'].value) }}</code>.
|
||||
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']) }}</code>.
|
||||
</p>
|
||||
<h5 class="border-bottom">Counters</h5>
|
||||
<p>
|
||||
|
@ -4,7 +4,7 @@
|
||||
<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 %}
|
||||
<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.mtime %}<span class="badge rounded-pill text-bg-light border fw-normal"><i class="ri-calendar-line"></i> {{ retired.human_time() }}</span>{% endif %}
|
||||
</p>
|
||||
|
@ -4,7 +4,7 @@
|
||||
<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 %}
|
||||
<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.mtime %}<span class="badge rounded-pill text-bg-light border fw-normal"><i class="ri-calendar-line"></i> {{ theme.human_time() }}</span>{% endif %}
|
||||
</p>
|
||||
|
@ -25,7 +25,7 @@
|
||||
<div class="collapse navbar-collapse" id="navbarSupportedContent">
|
||||
<ul class="navbar-nav me-auto mb-2 mb-lg-0">
|
||||
{% 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">
|
||||
<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 %}
|
||||
@ -61,7 +61,7 @@
|
||||
<div class="col-md-6 d-flex justify-content-center">
|
||||
<small>
|
||||
<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() }}
|
||||
{% endif %}
|
||||
</small>
|
||||
|
@ -3,8 +3,8 @@
|
||||
{% block main %}
|
||||
<div class="container-fluid">
|
||||
<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
|
||||
{% if not config['HIDE_ALL_SETS'].value %}
|
||||
<i class="ri-hashtag"></i> {% if config['RANDOM'] %}Random selection of{% else %}Latest added{% endif %} sets
|
||||
{% if not config['HIDE_ALL_SETS'] %}
|
||||
<a href="{{ url_for('set.list') }}" class="btn btn-sm btn-primary ms-1">All sets</a>
|
||||
{% endif %}
|
||||
</h2>
|
||||
@ -23,8 +23,8 @@
|
||||
{% endif %}
|
||||
{% if minifigure_collection | length %}
|
||||
<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
|
||||
{% if not config['HIDE_ALL_MINIFIGURES'].value %}
|
||||
<i class="ri-group-line"></i> {% if config['RANDOM'] %}Random selection of{% else %}Latest added{% endif %} minifigures
|
||||
{% if not config['HIDE_ALL_MINIFIGURES'] %}
|
||||
<a href="{{ url_for('minifigure.list') }}" class="btn btn-sm btn-primary ms-1">All minifigures</a>
|
||||
{% endif %}
|
||||
</h2>
|
||||
|
@ -26,8 +26,8 @@
|
||||
<div class="mb-3">
|
||||
<label for="file" class="form-label">Instructions file</label>
|
||||
<div class="input-group">
|
||||
<input type="file" class="form-control" id="file" name="file" accept="{{ ','.join(config['INSTRUCTIONS_ALLOWED_EXTENSIONS'].value) }}">
|
||||
<span class="input-group-text">{{ ', '.join(config['INSTRUCTIONS_ALLOWED_EXTENSIONS'].value) }}</span>
|
||||
<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']) }}</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="text-end">
|
||||
|
@ -16,7 +16,7 @@
|
||||
{% endif %}
|
||||
</button>
|
||||
</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 %}">
|
||||
{% endmacro %}
|
||||
|
||||
|
@ -60,7 +60,7 @@
|
||||
{% if number %}{ select: [{{ number }}], type: "number", searchable: false },{% endif %}
|
||||
],
|
||||
pagerDelta: 1,
|
||||
perPage: {{ config['DEFAULT_TABLE_PER_PAGE'].value }},
|
||||
perPage: {{ config['DEFAULT_TABLE_PER_PAGE'] }},
|
||||
perPageSelect: [10, 25, 50, 100, 500, 1000],
|
||||
searchable: true,
|
||||
searchQuerySeparator: "",
|
||||
|
Loading…
Reference in New Issue
Block a user